Issue #959

Decoding JSON in Swift is most of the time very straightforward with help of Codable protocol and JSONDecoder.

Sometimes the json contains dynamic key, like

{
    "shipmunk": {
        "name": "Shipmunk",
        "link": "https://indiegoodies.com/shipmunk"
    },
    "pastepal": {
        "name": "PastePal",
        "link": "https://indiegoodies.com/pastepal"
    },
    "codelime": {
        "name": "Codelime",
        "link": "https://indiegoodies.com/codelime"
    }
}

Decoding JSON with dynamic keys in Swift can be tricky because the keys in your data can change, and Swift likes to know exactly what it’s working with ahead of time. In your example, the keys “shipmunk,” “pastepal,” and “codelime” are not fixed and can vary, which makes it hard to prepare your code in advance.

Swift is very careful about data types and making sure everything matches up correctly. With dynamic keys, it’s harder to guarantee this, which can lead to issues in your app.

Implement CodingKey protocol

CodingKey is a type that can be used as a key for encoding and decoding. It requires that the key is either Int or String

We can define our own dynamic key

struct DynamicCodingKeys: CodingKey {
    var stringValue: String
    var intValue: Int?
    
    init?(stringValue: String) {
        self.stringValue = stringValue
    }

    init?(intValue: Int) {
        return nil
    }
}

Then we can implement our own init(from decoder) method and get a container using our custom coding keys.

struct App: Decodable {
    let name: String
    let link: URL
}

struct Result: Decodable {
    var apps: [App]

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
        var map: [String: App] = [:]
        
        for key in container.allKeys {
            let app = try container.decode(
                App.self,
                forKey: DynamicCodingKeys(stringValue: key.stringValue)!
            )
            
            map[key.stringValue] = app
        }
        
        self.apps = map.map { $1 }
    }
}

In the context of Swift’s Decodable protocol, a “container” is a type that provides a view into the JSON data. It allows your code to access the JSON data in a structured way. The decoder.container(keyedBy) creates a keyed container, a type of container that lets you access values from your JSON by using keys.

container.allKeys gives you all the keys present in the JSON data. Because we using DynamicCodingKeys, these can be any keys, not just ones you know about beforehand.

Now can just decode it like normal

let data = Data(json.utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Result.self, from: data)