Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Codable on a dynamic type/object

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].

like image 698
Reshad Avatar asked Oct 13 '17 09:10

Reshad


People also ask

What is Codable used for?

Codable allows you to insert an additional clarifying stage into the process of decoding data into a Swift object. This stage is the “parsed object,” whose properties and keys match up directly to the data, but whose types have been decoded into Swift objects.

Can a class be Codable Swift?

Remember, Swift's String , Int , and Bool are all Codable ! Earlier I wrote that your structs, enums, and classes can conform to Codable . Swift can generate the code needed to extract data to populate a struct's properties from JSON data as long as all properties conform to Codable .

What is Codable protocol?

Introduced in Swift 4, the Codable API enables us to leverage the compiler in order to generate much of the code needed to encode and decode data to/from a serialized format, like JSON. Codable is actually a type alias that combines two protocols — Encodable and Decodable — into one.

What is Codable in Swift UI?

Codable is a type alias for the Encodable and Decodable protocols. When you use Codable as a type or a generic constraint, it matches any type that conforms to both protocols.


1 Answers

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)
like image 102
Code Different Avatar answered Oct 05 '22 16:10

Code Different