Преобразование GEOSwift.JSON в структуру в Swift
У меня есть такая функция GEOSwift:
{
"type" : "Feature",
"geometry" : {
"type" : "Point",
"coordinates" : [
-xx.xxxxxxxxxxxxxxx,
xx.xxxxxxxxxxxxxxx
]
},
"properties" : {
"mapLayer" : "MyMapLayer",
"data" : {
"id" : 42,
"sizeClass" : "Large",
// and so on...
},
"featureType" : "MyFeatureType"
}
}
Я хочу получить элемент и поместить его в структуру, которая соответствует:
struct MyStruct: Decodable {
var id: Int
var sizeClass: String?
// and so on...
}
Этот код даст мнеdata
один, но тип данных — GEOSwift.JSON, и я не знаю, как преобразовать его в строку для декодирования с использованием обычного класса JSONDecoder.
if case let .object(data) = feature.properties?["data"] {
// do stuff with data: GEOSwift.JSON to get it into MyStruct
}
Вот перечисление GEOSwift.JSON:
import Foundation
public enum JSON: Hashable, Sendable {
case string(String)
case number(Double)
case boolean(Bool)
case array([JSON])
case object([String: JSON])
case null
/// Recursively unwraps and returns the associated value
public var untypedValue: Any {
switch self {
case let .string(string):
return string
case let .number(number):
return number
case let .boolean(boolean):
return boolean
case let .array(array):
return array.map { $0.untypedValue }
case let .object(object):
return object.mapValues { $0.untypedValue }
case .null:
return NSNull()
}
}
}
extension JSON: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .string(value)
}
}
extension JSON: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self = .number(Double(value))
}
}
extension JSON: ExpressibleByFloatLiteral {
public init(floatLiteral value: Double) {
self = .number(value)
}
}
extension JSON: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
self = .boolean(value)
}
}
extension JSON: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: JSON...) {
self = .array(elements)
}
}
extension JSON: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, JSON)...) {
let object = elements.reduce(into: [:]) { (result, element) in
result[element.0] = element.1
}
self = .object(object)
}
}
extension JSON: ExpressibleByNilLiteral {
public init(nilLiteral: ()) {
self = .null
}
}
2 ответа
Чтобы добавить к ответу: воспользовавшись тем фактом, что перечисление соответствуетEncodable
, что означает, что его можно преобразовать непосредственно в данные JSON, используяJSONEncoder
, код будет выглядеть так:
import Foundation
import GEOSwift
struct MyStruct: Decodable {
let id: Int
let sizeClass: String
// and so on...
}
if let feature = mySomething as? GeoJSON.Feature {
// Extract the properties as GEOSwift.JSON
if let data = feature.properties?["data"] {
do {
// Re-encode GEOSwift.JSON back to JSON Data
let jsonData = try JSONEncoder().encode(data)
// Decode JSON Data to your struct
let decoder = JSONDecoder()
let myStruct = try decoder.decode(MyStruct.self, from: jsonData)
// Now you can use `myStruct`
print(myStruct)
} catch {
print("Error: \(error)")
}
}
} else {
// Handle other cases here
print("GeoJSON object is not a Feature")
}
Нет необходимости выполнять средний этап преобразования в словарь, а затем в . Вместо этого они перекодируются непосредственно обратно в данные JSON, которые затем декодируются в ваш файл .
Этот подход должен работать до тех пор, пока структураGEOSwift.JSON
объект соответствует структуре вашегоMyStruct
. Если структура не совпадает, на этапе декодирования вы получите сообщение об ошибке.
Альтернативно, как предложено в комментариях,может стать жизнеспособной альтернативой GEOSwift, если вы работаете конкретно с картографическими данными и вам удобно использовать платформу Apple MapKit.
Он обеспечивает простой способ анализа данных GeoJSON и преобразования их в объекты, которые можно отобразить на карте.
Базовый обзор того, как вы можете использовать анализ данных GeoJSON, будет выглядеть так:
import MapKit
// Assume you have GeoJSON data in the `geoJsonData` variable of type Data
let geoJsonObject: [MKGeoJSONObject]
do {
geoJsonObject = try MKGeoJSONDecoder().decode(geoJsonData)
} catch {
print("Failed to decode GeoJSON: \(error)")
return
}
// You can then process the geoJsonObject depending on its type
for object in geoJsonObject {
switch object {
case let feature as MKGeoJSONFeature:
// Handle feature
print("Feature: \(feature)")
case let geometry as MKShape & MKGeoJSONObject:
// Handle geometry
print("Geometry: \(geometry)")
default:
print("Unexpected type")
}
}
Этот код будет анализировать данные GeoJSON и распечатывать каждый объект или геометрию в данных. MKGeoJSONFeature
объекты содержат информацию о свойствах объекта, к которой вы можете получить доступ черезproperties
имущество, которое относится к типуData
. Если ваши объекты имеют свойства, которые являются объектами JSON, вы можете использоватьJSONDecoder
чтобы декодировать эти свойства в ваши собственные типы Swift.
Обратите внимание, что это относится только к платформе MapKit и предназначено для работы с данными, связанными с картами. Если ваше приложение не использует карты или вам нужно работать с данными GeoJSON, которые не связаны напрямую с функциями карты,MKGeoJSONDecoder
возможно, это не лучший выбор. В таких случаях вы можете использовать GEOSwift или другую библиотеку GeoJSON общего назначения.
Вам придется перекодировать Feature.properties["data"] обратно в JSON, а затем декодировать его в MyStruct. Вы не можете сделать это напрямую, потому что GEOSwift.JSON не соответствует Decodeable, но соответствует Encodable, поэтому вы кодируете его, а затем декодируете обратно.
let myStructData = feature.properties?["data"] ?? nil
let jsonData = try! JSONEncoder().encode(myStructData)
let decoder = JSONDecoder()
myStruct = try decoder.decode(MyStruct.self, from: jsonData)