9

Hi I have the following structure nested in a bigger structure that is returned from an api call but I can't manage to encode/decode this part. The problem I am having is that the customKey and customValue are both dynamic.

{
    "current" : "a value"
    "hash" : "some value"
    "values": {
        "customkey": "customValue",
        "customKey": "customValue"
    }
}

I tried something like var values: [String:String] But that is obviously not working because its not actually an array of [String:String].

10
  • @vadian I don't see how this is duplicate of any of those questions. I modified the question to be more clear now. Commented Oct 13, 2017 at 9:34
  • I understand and reopened the question: Short answer: You cannot use Codable with dynamic keys. Commented Oct 13, 2017 at 9:36
  • Can you recommend another way to do this? Commented Oct 13, 2017 at 10:13
  • If the keys are dynamic you can only use a dynamic collection type, in this case a dictionary. Commented Oct 13, 2017 at 10:28
  • Do you have an example of how to use it with a Dictionary? Commented Oct 13, 2017 at 12:08

2 Answers 2

14

Since you linked to my answer to another question, I will expand that one to answer yours.

Truth is, all keys are known at runtime if you know where to look:

struct GenericCodingKeys: CodingKey {
    var intValue: Int?
    var stringValue: String

    init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
    init?(stringValue: String) { self.stringValue = stringValue }

    static func makeKey(name: String) -> GenericCodingKeys {
        return GenericCodingKeys(stringValue: name)!
    }
}


struct MyModel: Decodable {
    var current: String
    var hash: String
    var values: [String: String]

    private enum CodingKeys: String, CodingKey {
        case current
        case hash
        case values
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        current = try container.decode(String.self, forKey: .current)
        hash = try container.decode(String.self, forKey: .hash)

        values = [String: String]()
        let subContainer = try container.nestedContainer(keyedBy: GenericCodingKeys.self, forKey: .values)
        for key in subContainer.allKeys {
            values[key.stringValue] = try subContainer.decode(String.self, forKey: key)
        }
    }
}

Usage:

let jsonData = """
{
    "current": "a value",
    "hash": "a value",
    "values": {
        "key1": "customValue",
        "key2": "customValue"
    }
}
""".data(using: .utf8)!

let model = try JSONDecoder().decode(MyModel.self, from: jsonData)
Sign up to request clarification or add additional context in comments.

6 Comments

Thank you so much for your quick response. How would this apply if I have others keys in the JSON next to values that are normal decodable, Do I add a normal enum container for that ?
I'm getting confused with your new requirements. Can you edit the JSON to show an example of it?
I edited the JSON. What I meant was that the actual object that I am encoding and decoding has more than only values key->object. :)
@CodeDifferent I'm in a similar situation but in my case, i know "key1" and "key2" but not "values", i didn't figure out how to adapt your code, could you help ? thanks !
@TmSmth there’s not enough info for me to help here. Please post a new question with the details of your specific situation
|
1

Simplified answer, it is working with dictionary [String: String] (instatead of String you can use other struct):

let jsonData = """
{
    "current": "a value",
    "hash": "a value",
    "values": {
        "key1": "customValue",
        "key2": "customValue"
    }
}
""".data(using: .utf8)!

struct MyModel: Decodable {
    var current: String
    var hash: String
    var values: [String: String]
}

let model = try JSONDecoder().decode(MyModel.self, from: jsonData)

for (key,value) in model.values {
    print("key: \(key) value: \(value)")
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.