Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Codable: how to "pass through" a json sub-object unanalysed

Tags:

swift

codable

For complicated reasons I find myself working against the grain of Codable: when decoding a json object I want to preserve the sub-object under the key extra "just as json", stored as a [String: Any] dictionary, but [String: Any] is (of course) not Decodable. Is there any way to do this?

The first answer is of course "don't". To which my response is "I need to": I need to make two Codable passes over the data, in which the first decodes a list of heterogeneous objects (which each have a key name) while the second pass uses a dictionary keyed on those name values and is properly type-safe. The first pass cannot be type-safe because it's operating on a heterogenous list, but it needs to preserve all the data that the second pass will work with. Thankfully all the heterogenous data is hidden under that extra key, but still I don't see how to do it.

(There will very likely be a followup question about _en_coding the same things, so if you happen to have insight feel free to mention it.)

like image 631
Tikitu Avatar asked Nov 18 '25 08:11

Tikitu


1 Answers

You can create a Custom Dictionary Decodable which decodes and stores the values in an Dictionary and then use this type for the key in which you want to store raw dictionary.

Here is the Decodable:

struct DictionaryDecodable: Decodable {

    let dictionary : [String: Any]

    private struct Key : CodingKey {

        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }

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

    init(from decoder: Decoder) throws {
        let con = try decoder.container(keyedBy: Key.self)
        var dict = [String: Any]()
        for key in con.allKeys {
            if let value = try? con.decode(String.self, forKey:key) {
                dict[key.stringValue] = value
            } else if let value = try? con.decode(Int.self, forKey:key) {
                dict[key.stringValue] = value
            } else if let value = try? con.decode(Double.self, forKey:key) {
                dict[key.stringValue] = value
            } else if let value = try? con.decode(Bool.self, forKey:key) {
                dict[key.stringValue] = value
            } else if let data = try? con.decode(DictionaryDecodable.self, forKey:key)  {
                dict[key.stringValue] = data.dictionary
            }

        }
        self.dictionary = dict
    }
}

Now you can use this struct to decode dictionary like this:

struct Test: Decodable {
    let name: String
    let data: [String: Any]

    enum Keys: String, CodingKey {
        case name
        case data
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Keys.self)
        name = try container.decode(String.self, forKey: .name)
        data = try container.decode(DictionaryDecodable.self, forKey: .data).dictionary // Here use DictionaryDecodable
    }
}

Let's test:

let data = """
{
    "name": "name",
    "data": {
        "string": "rt",
        "bool": true,
        "float": 1.12,
        "int": 1,
        "dict": {
            "test": "String"
        }
    }
}
"""

let s = try JSONDecoder().decode(Test.self, from: data.data(using: .utf8)!)
print(s.name)
print(s.data)

Here's the output:

name
["bool": true, "string": "rt", "int": 1, "float": 1.12, "dict": ["test": "String"]]
like image 177
Mukesh Avatar answered Nov 20 '25 20:11

Mukesh



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!