I have a GEOSwift feature like this:
{
"type" : "Feature",
"geometry" : {
"type" : "Point",
"coordinates" : [
-xx.xxxxxxxxxxxxxxx,
xx.xxxxxxxxxxxxxxx
]
},
"properties" : {
"mapLayer" : "MyMapLayer",
"data" : {
"id" : 42,
"sizeClass" : "Large",
// and so on...
},
"featureType" : "MyFeatureType"
}
}
I want to retrieve the data
member and put it into a struct that matches:
struct MyStruct: Decodable {
var id: Int
var sizeClass: String?
// and so on...
}
This code will get me the data
alone, but the datatype is GEOSwift.JSON, and I don’t know how to stringify it to decode using the usual JSONDecoder class.
if case let .object(data) = feature.properties?["data"] {
// do stuff with data: GEOSwift.JSON to get it into MyStruct
}
Here is the GEOSwift.JSON enum:
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
Answers
You have to re encode feature.properties["data"] back to JSON, then decode it to MyStruct. You can't do this directly because GEOSwift.JSON doesn't conform to Decodable, but it does conform to Encodable, so you encode it then decode it back.
To add to the answer: Taking advantage of the fact that the
GEOSwift.JSON
enum does conform toEncodable
, which means it can be converted directly to JSON data usingJSONEncoder
, the code would look like:No need for a middle step of converting the
GEOSwift.JSON
to a dictionary and then toData
. Instead, theGEOSwift.JSON
is re-encoded directly back to JSON data, which is then decoded into yourMyStruct
.This approach should work as long as the structure of the
GEOSwift.JSON
object matches the structure of yourMyStruct
. If the structure doesn’t match, you’ll get an error during the decoding step.Alternatively, as suggested in the comments,
MKGeoJSONDecoder
could be a viable alternative to GEOSwift if you are working specifically with map data and are comfortable using Apple’s MapKit framework.It provides an easy way to parse GeoJSON data and convert it into objects that can be displayed on a map.
A basic overview of how you might use
MKGeoJSONDecoder
to parse GeoJSON data would look like:This code will parse GeoJSON data and print out each feature or geometry in the data. The
MKGeoJSONFeature
objects contain information about the feature’s properties, which you can access via theproperties
property, which is of typeData
. If your features have properties that are JSON objects, you can useJSONDecoder
to decode these properties into your custom Swift types.Do note that
MKGeoJSONDecoder
is specific to the MapKit framework and is designed to work with map-related data. If your application does not involve maps, or if you need to work with GeoJSON data that is not directly related to map features,MKGeoJSONDecoder
might not be the best choice. In such cases, you might want to stick with GEOSwift or another general-purpose GeoJSON library.