Swift 4 decodable with unknown dynamic keys










3














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?










share|improve this question























  • Check out this answer: stackoverflow.com/a/45603025/8289095
    – Chris
    Nov 12 '18 at 19:36










  • I've seen that the problem is that I don't know the name of the key is possible to do that with decodable?
    – sger
    Nov 12 '18 at 19:42










  • Add this: case meta = "Meta", unknown = "DynamicKey"
    – Oleg Gordiichuk
    Nov 12 '18 at 20:10











  • @OlegGordiichuk DynamicKey is just a random name not the actual name it would be different every time...
    – Ahmad F
    Nov 12 '18 at 20:11










  • Ok, so at this point I would assume that the output should be a dictionary as [DynamicKey: Meta] since we are unable to define a specific property for it...
    – Ahmad F
    Nov 12 '18 at 20:13















3














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?










share|improve this question























  • Check out this answer: stackoverflow.com/a/45603025/8289095
    – Chris
    Nov 12 '18 at 19:36










  • I've seen that the problem is that I don't know the name of the key is possible to do that with decodable?
    – sger
    Nov 12 '18 at 19:42










  • Add this: case meta = "Meta", unknown = "DynamicKey"
    – Oleg Gordiichuk
    Nov 12 '18 at 20:10











  • @OlegGordiichuk DynamicKey is just a random name not the actual name it would be different every time...
    – Ahmad F
    Nov 12 '18 at 20:11










  • Ok, so at this point I would assume that the output should be a dictionary as [DynamicKey: Meta] since we are unable to define a specific property for it...
    – Ahmad F
    Nov 12 '18 at 20:13













3












3








3


1





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?










share|improve this question















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?







json swift decodable






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 12 '18 at 20:08









rmaddy

238k27310376




238k27310376










asked Nov 12 '18 at 19:27









sger

3292619




3292619











  • Check out this answer: stackoverflow.com/a/45603025/8289095
    – Chris
    Nov 12 '18 at 19:36










  • I've seen that the problem is that I don't know the name of the key is possible to do that with decodable?
    – sger
    Nov 12 '18 at 19:42










  • Add this: case meta = "Meta", unknown = "DynamicKey"
    – Oleg Gordiichuk
    Nov 12 '18 at 20:10











  • @OlegGordiichuk DynamicKey is just a random name not the actual name it would be different every time...
    – Ahmad F
    Nov 12 '18 at 20:11










  • Ok, so at this point I would assume that the output should be a dictionary as [DynamicKey: Meta] since we are unable to define a specific property for it...
    – Ahmad F
    Nov 12 '18 at 20:13
















  • Check out this answer: stackoverflow.com/a/45603025/8289095
    – Chris
    Nov 12 '18 at 19:36










  • I've seen that the problem is that I don't know the name of the key is possible to do that with decodable?
    – sger
    Nov 12 '18 at 19:42










  • Add this: case meta = "Meta", unknown = "DynamicKey"
    – Oleg Gordiichuk
    Nov 12 '18 at 20:10











  • @OlegGordiichuk DynamicKey is just a random name not the actual name it would be different every time...
    – Ahmad F
    Nov 12 '18 at 20:11










  • Ok, so at this point I would assume that the output should be a dictionary as [DynamicKey: Meta] since we are unable to define a specific property for it...
    – Ahmad F
    Nov 12 '18 at 20:13















Check out this answer: stackoverflow.com/a/45603025/8289095
– Chris
Nov 12 '18 at 19:36




Check out this answer: stackoverflow.com/a/45603025/8289095
– Chris
Nov 12 '18 at 19:36












I've seen that the problem is that I don't know the name of the key is possible to do that with decodable?
– sger
Nov 12 '18 at 19:42




I've seen that the problem is that I don't know the name of the key is possible to do that with decodable?
– sger
Nov 12 '18 at 19:42












Add this: case meta = "Meta", unknown = "DynamicKey"
– Oleg Gordiichuk
Nov 12 '18 at 20:10





Add this: case meta = "Meta", unknown = "DynamicKey"
– Oleg Gordiichuk
Nov 12 '18 at 20:10













@OlegGordiichuk DynamicKey is just a random name not the actual name it would be different every time...
– Ahmad F
Nov 12 '18 at 20:11




@OlegGordiichuk DynamicKey is just a random name not the actual name it would be different every time...
– Ahmad F
Nov 12 '18 at 20:11












Ok, so at this point I would assume that the output should be a dictionary as [DynamicKey: Meta] since we are unable to define a specific property for it...
– Ahmad F
Nov 12 '18 at 20:13




Ok, so at this point I would assume that the output should be a dictionary as [DynamicKey: Meta] since we are unable to define a specific property for it...
– Ahmad F
Nov 12 '18 at 20:13












2 Answers
2






active

oldest

votes


















4














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







share|improve this answer






















  • Thanks man really useful information
    – sger
    Nov 12 '18 at 20:45










  • @Rob nice but my implementation seems shorter
    – Vyacheslav
    Nov 12 '18 at 21:07










  • @Vyacheslav The extra baggage your removed is fine (description, ==, and hashValue are unnecessary; they came over from my larger JSON implementation). But you changed an important part of the problem: you made everything optionals. Yes, that got you out of having to throw errors (which is why your init is shorter), but that changes the data structure in really important way. You also made the let values var, which again makes init simpler at the expense of the immutability of the object.
    – Rob Napier
    Nov 12 '18 at 21:32







  • 1




    The choice choice to use "the first thing that decodes as Double" vs "the first key that isn't Meta" is probably fine; neither way is really checking the data very closely. But it's important to know which validation you're using. For example, if another mandatory Double field were added, your approach would become fragile. If another optional field (one we didn't parse) were added to the JSON, then mine would break.
    – Rob Napier
    Nov 12 '18 at 21:36


















1














import UIKit

var str = """
"DynamicKey":6410,"Meta":"name":"","page":""
"""
public struct MyStruct: Decodable
public var unknown: Double?
public var meta: [String: String]?

public init(from decoder: Decoder)

guard let container = try? decoder.container(keyedBy: CodingKeys.self) else
fatalError()

for key in container.allKeys
unknown = try? container.decode(Double.self, forKey: key)//) ?? 0.0
if key.stringValue == "Meta"
meta = try? container.decode([String: String].self, forKey: key)



print(container.allKeys)


struct CodingKeys: CodingKey
var stringValue: String
init?(stringValue: String)
self.stringValue = stringValue

var intValue: Int?
init?(intValue: Int)
return nil



let jsonData = str.data(using: .utf8)!
let jsonDecoder = JSONDecoder()
let myStruct = try! jsonDecoder.decode(MyStruct.self, from: jsonData)
print("Meta : (myStruct.meta)")
print("Double : (myStruct.unknown)")


I've already answered a similar question



https://stackoverflow.com/a/48412139/1979882






share|improve this answer




















    Your Answer






    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "1"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader:
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    ,
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );













    draft saved

    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53268833%2fswift-4-decodable-with-unknown-dynamic-keys%23new-answer', 'question_page');

    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    4














    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







    share|improve this answer






















    • Thanks man really useful information
      – sger
      Nov 12 '18 at 20:45










    • @Rob nice but my implementation seems shorter
      – Vyacheslav
      Nov 12 '18 at 21:07










    • @Vyacheslav The extra baggage your removed is fine (description, ==, and hashValue are unnecessary; they came over from my larger JSON implementation). But you changed an important part of the problem: you made everything optionals. Yes, that got you out of having to throw errors (which is why your init is shorter), but that changes the data structure in really important way. You also made the let values var, which again makes init simpler at the expense of the immutability of the object.
      – Rob Napier
      Nov 12 '18 at 21:32







    • 1




      The choice choice to use "the first thing that decodes as Double" vs "the first key that isn't Meta" is probably fine; neither way is really checking the data very closely. But it's important to know which validation you're using. For example, if another mandatory Double field were added, your approach would become fragile. If another optional field (one we didn't parse) were added to the JSON, then mine would break.
      – Rob Napier
      Nov 12 '18 at 21:36















    4














    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







    share|improve this answer






















    • Thanks man really useful information
      – sger
      Nov 12 '18 at 20:45










    • @Rob nice but my implementation seems shorter
      – Vyacheslav
      Nov 12 '18 at 21:07










    • @Vyacheslav The extra baggage your removed is fine (description, ==, and hashValue are unnecessary; they came over from my larger JSON implementation). But you changed an important part of the problem: you made everything optionals. Yes, that got you out of having to throw errors (which is why your init is shorter), but that changes the data structure in really important way. You also made the let values var, which again makes init simpler at the expense of the immutability of the object.
      – Rob Napier
      Nov 12 '18 at 21:32







    • 1




      The choice choice to use "the first thing that decodes as Double" vs "the first key that isn't Meta" is probably fine; neither way is really checking the data very closely. But it's important to know which validation you're using. For example, if another mandatory Double field were added, your approach would become fragile. If another optional field (one we didn't parse) were added to the JSON, then mine would break.
      – Rob Napier
      Nov 12 '18 at 21:36













    4












    4








    4






    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







    share|improve this answer














    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








    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Nov 12 '18 at 20:45

























    answered Nov 12 '18 at 20:33









    Rob Napier

    199k28293419




    199k28293419











    • Thanks man really useful information
      – sger
      Nov 12 '18 at 20:45










    • @Rob nice but my implementation seems shorter
      – Vyacheslav
      Nov 12 '18 at 21:07










    • @Vyacheslav The extra baggage your removed is fine (description, ==, and hashValue are unnecessary; they came over from my larger JSON implementation). But you changed an important part of the problem: you made everything optionals. Yes, that got you out of having to throw errors (which is why your init is shorter), but that changes the data structure in really important way. You also made the let values var, which again makes init simpler at the expense of the immutability of the object.
      – Rob Napier
      Nov 12 '18 at 21:32







    • 1




      The choice choice to use "the first thing that decodes as Double" vs "the first key that isn't Meta" is probably fine; neither way is really checking the data very closely. But it's important to know which validation you're using. For example, if another mandatory Double field were added, your approach would become fragile. If another optional field (one we didn't parse) were added to the JSON, then mine would break.
      – Rob Napier
      Nov 12 '18 at 21:36
















    • Thanks man really useful information
      – sger
      Nov 12 '18 at 20:45










    • @Rob nice but my implementation seems shorter
      – Vyacheslav
      Nov 12 '18 at 21:07










    • @Vyacheslav The extra baggage your removed is fine (description, ==, and hashValue are unnecessary; they came over from my larger JSON implementation). But you changed an important part of the problem: you made everything optionals. Yes, that got you out of having to throw errors (which is why your init is shorter), but that changes the data structure in really important way. You also made the let values var, which again makes init simpler at the expense of the immutability of the object.
      – Rob Napier
      Nov 12 '18 at 21:32







    • 1




      The choice choice to use "the first thing that decodes as Double" vs "the first key that isn't Meta" is probably fine; neither way is really checking the data very closely. But it's important to know which validation you're using. For example, if another mandatory Double field were added, your approach would become fragile. If another optional field (one we didn't parse) were added to the JSON, then mine would break.
      – Rob Napier
      Nov 12 '18 at 21:36















    Thanks man really useful information
    – sger
    Nov 12 '18 at 20:45




    Thanks man really useful information
    – sger
    Nov 12 '18 at 20:45












    @Rob nice but my implementation seems shorter
    – Vyacheslav
    Nov 12 '18 at 21:07




    @Rob nice but my implementation seems shorter
    – Vyacheslav
    Nov 12 '18 at 21:07












    @Vyacheslav The extra baggage your removed is fine (description, ==, and hashValue are unnecessary; they came over from my larger JSON implementation). But you changed an important part of the problem: you made everything optionals. Yes, that got you out of having to throw errors (which is why your init is shorter), but that changes the data structure in really important way. You also made the let values var, which again makes init simpler at the expense of the immutability of the object.
    – Rob Napier
    Nov 12 '18 at 21:32





    @Vyacheslav The extra baggage your removed is fine (description, ==, and hashValue are unnecessary; they came over from my larger JSON implementation). But you changed an important part of the problem: you made everything optionals. Yes, that got you out of having to throw errors (which is why your init is shorter), but that changes the data structure in really important way. You also made the let values var, which again makes init simpler at the expense of the immutability of the object.
    – Rob Napier
    Nov 12 '18 at 21:32





    1




    1




    The choice choice to use "the first thing that decodes as Double" vs "the first key that isn't Meta" is probably fine; neither way is really checking the data very closely. But it's important to know which validation you're using. For example, if another mandatory Double field were added, your approach would become fragile. If another optional field (one we didn't parse) were added to the JSON, then mine would break.
    – Rob Napier
    Nov 12 '18 at 21:36




    The choice choice to use "the first thing that decodes as Double" vs "the first key that isn't Meta" is probably fine; neither way is really checking the data very closely. But it's important to know which validation you're using. For example, if another mandatory Double field were added, your approach would become fragile. If another optional field (one we didn't parse) were added to the JSON, then mine would break.
    – Rob Napier
    Nov 12 '18 at 21:36













    1














    import UIKit

    var str = """
    "DynamicKey":6410,"Meta":"name":"","page":""
    """
    public struct MyStruct: Decodable
    public var unknown: Double?
    public var meta: [String: String]?

    public init(from decoder: Decoder)

    guard let container = try? decoder.container(keyedBy: CodingKeys.self) else
    fatalError()

    for key in container.allKeys
    unknown = try? container.decode(Double.self, forKey: key)//) ?? 0.0
    if key.stringValue == "Meta"
    meta = try? container.decode([String: String].self, forKey: key)



    print(container.allKeys)


    struct CodingKeys: CodingKey
    var stringValue: String
    init?(stringValue: String)
    self.stringValue = stringValue

    var intValue: Int?
    init?(intValue: Int)
    return nil



    let jsonData = str.data(using: .utf8)!
    let jsonDecoder = JSONDecoder()
    let myStruct = try! jsonDecoder.decode(MyStruct.self, from: jsonData)
    print("Meta : (myStruct.meta)")
    print("Double : (myStruct.unknown)")


    I've already answered a similar question



    https://stackoverflow.com/a/48412139/1979882






    share|improve this answer

























      1














      import UIKit

      var str = """
      "DynamicKey":6410,"Meta":"name":"","page":""
      """
      public struct MyStruct: Decodable
      public var unknown: Double?
      public var meta: [String: String]?

      public init(from decoder: Decoder)

      guard let container = try? decoder.container(keyedBy: CodingKeys.self) else
      fatalError()

      for key in container.allKeys
      unknown = try? container.decode(Double.self, forKey: key)//) ?? 0.0
      if key.stringValue == "Meta"
      meta = try? container.decode([String: String].self, forKey: key)



      print(container.allKeys)


      struct CodingKeys: CodingKey
      var stringValue: String
      init?(stringValue: String)
      self.stringValue = stringValue

      var intValue: Int?
      init?(intValue: Int)
      return nil



      let jsonData = str.data(using: .utf8)!
      let jsonDecoder = JSONDecoder()
      let myStruct = try! jsonDecoder.decode(MyStruct.self, from: jsonData)
      print("Meta : (myStruct.meta)")
      print("Double : (myStruct.unknown)")


      I've already answered a similar question



      https://stackoverflow.com/a/48412139/1979882






      share|improve this answer























        1












        1








        1






        import UIKit

        var str = """
        "DynamicKey":6410,"Meta":"name":"","page":""
        """
        public struct MyStruct: Decodable
        public var unknown: Double?
        public var meta: [String: String]?

        public init(from decoder: Decoder)

        guard let container = try? decoder.container(keyedBy: CodingKeys.self) else
        fatalError()

        for key in container.allKeys
        unknown = try? container.decode(Double.self, forKey: key)//) ?? 0.0
        if key.stringValue == "Meta"
        meta = try? container.decode([String: String].self, forKey: key)



        print(container.allKeys)


        struct CodingKeys: CodingKey
        var stringValue: String
        init?(stringValue: String)
        self.stringValue = stringValue

        var intValue: Int?
        init?(intValue: Int)
        return nil



        let jsonData = str.data(using: .utf8)!
        let jsonDecoder = JSONDecoder()
        let myStruct = try! jsonDecoder.decode(MyStruct.self, from: jsonData)
        print("Meta : (myStruct.meta)")
        print("Double : (myStruct.unknown)")


        I've already answered a similar question



        https://stackoverflow.com/a/48412139/1979882






        share|improve this answer












        import UIKit

        var str = """
        "DynamicKey":6410,"Meta":"name":"","page":""
        """
        public struct MyStruct: Decodable
        public var unknown: Double?
        public var meta: [String: String]?

        public init(from decoder: Decoder)

        guard let container = try? decoder.container(keyedBy: CodingKeys.self) else
        fatalError()

        for key in container.allKeys
        unknown = try? container.decode(Double.self, forKey: key)//) ?? 0.0
        if key.stringValue == "Meta"
        meta = try? container.decode([String: String].self, forKey: key)



        print(container.allKeys)


        struct CodingKeys: CodingKey
        var stringValue: String
        init?(stringValue: String)
        self.stringValue = stringValue

        var intValue: Int?
        init?(intValue: Int)
        return nil



        let jsonData = str.data(using: .utf8)!
        let jsonDecoder = JSONDecoder()
        let myStruct = try! jsonDecoder.decode(MyStruct.self, from: jsonData)
        print("Meta : (myStruct.meta)")
        print("Double : (myStruct.unknown)")


        I've already answered a similar question



        https://stackoverflow.com/a/48412139/1979882







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 12 '18 at 21:03









        Vyacheslav

        13.8k960120




        13.8k960120



























            draft saved

            draft discarded
















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53268833%2fswift-4-decodable-with-unknown-dynamic-keys%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            這個網誌中的熱門文章

            How to read a connectionString WITH PROVIDER in .NET Core?

            Node.js Script on GitHub Pages or Amazon S3

            Museum of Modern and Contemporary Art of Trento and Rovereto