Преобразование 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)
Другие вопросы по тегам