I have the following JSON
{"DynamicKey":6410,"Meta":{"name":"","page":""}}
DynamicKey is unknown at compile time.I'm trying to find a reference how to parse this struct using decodable.
public struct MyStruct: Decodable {
public let unknown: Double
public let meta: [String: String]
private enum CodingKeys: String, CodingKey {
case meta = "Meta"
}
}
Any ideas?
To decode an arbitrary string, you need a key like this:
// Arbitrary key
private struct Key: CodingKey, Hashable, CustomStringConvertible {
static let meta = Key(stringValue: "Meta")!
var description: String {
return stringValue
}
var hashValue: Int { return stringValue.hash }
static func ==(lhs: Key, rhs: Key) -> Bool {
return lhs.stringValue == rhs.stringValue
}
let stringValue: String
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.init(stringValue) }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
This is a very general-purpose tool (expect for the static let meta
) that can be used for all kinds of generic-key problems.
With that, you can find the first key that isn't .meta
and use that as your dynamic key.
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Key.self)
meta = try container.decode([String: String].self, forKey: .meta)
guard let dynamicKey = container.allKeys.first(where: { $0 != .meta }) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [],
debugDescription: "Could not find dynamic key"))
}
unknown = try container.decode(Double.self, forKey: dynamicKey)
}
All together as a playground:
import Foundation
let json = Data("""
{"DynamicKey":6410,"Meta":{"name":"","page":""}}
""".utf8)
public struct MyStruct: Decodable {
public let unknown: Double
public let meta: [String: String]
// Arbitrary key
private struct Key: CodingKey, Hashable, CustomStringConvertible {
static let meta = Key(stringValue: "Meta")!
var description: String {
return stringValue
}
var hashValue: Int { return stringValue.hash }
static func ==(lhs: Key, rhs: Key) -> Bool {
return lhs.stringValue == rhs.stringValue
}
let stringValue: String
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.init(stringValue) }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Key.self)
meta = try container.decode([String: String].self, forKey: .meta)
guard let dynamicKey = container.allKeys.first(where: { $0 != .meta }) else {
throw DecodingError.dataCorrupted(.init(codingPath: [],
debugDescription: "Could not find dynamic key"))
}
unknown = try container.decode(Double.self, forKey: dynamicKey)
}
}
let myStruct = try! JSONDecoder().decode(MyStruct.self, from: json)
myStruct.unknown
myStruct.meta
This technique can be expanded to decode arbitrary JSON. Sometimes it's easier to do that, and then pull out the pieces you want, then to decode each piece. For example, with the JSON gist above, you could implement MyStruct
this way:
public struct MyStruct: Decodable {
public let unknown: Double
public let meta: [String: String]
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let json = try container.decode(JSON.self)
guard let meta = json["Meta"]?.dictionaryValue as? [String: String] else {
throw DecodingError.dataCorrupted(.init(codingPath: [],
debugDescription: "Could not find meta key"))
}
self.meta = meta
guard let (_, unknownJSON) = json.objectValue?.first(where: { (key, _) in key != "Meta" }),
let unknown = unknownJSON.doubleValue
else {
throw DecodingError.dataCorrupted(.init(codingPath: [],
debugDescription: "Could not find dynamic key"))
}
self.unknown = unknown
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With