3

Codable is great when you know the key formatting of the JSON data. But what if you don't know the keys? I'm currently faced with this problem.

Normally I would expect JSON data to be returned like this:

{
"id": "<123>",
"data": [
    {
        "id": "<id1>",
        "event": "<event_type>",
        "date": "<date>"
    },
    {
        "id": "<id2>",
        "event": "<event_type>",
        "date": "<date>"
    },
]
}

But this is what I'm aiming to decode:

{
"id": "123",
"data": [
    { "<id1>": { "<event>": "<date>" } },
    { "<id2>": { "<event>": "<date>" } },
]
}

Question is: how do I use Codable to decode JSON where the keys are unique? I feel like I'm missing something obvious.

This is what I'm hoping to do so I can use Codable:

struct SampleModel: Codable {
    let id: String
    let data: [[String: [String: Any]]]

    // MARK: - Decoding

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case data = "data"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        id = try container.decode(String.self, forKey: .id)
        // This throws an error: Ambiguous reference to member 'decode(_:forKey:)'
        data = try container.decode([[String: [String: Any]]].self, forKey: .data)
    }
}

This throws an error: Ambiguous reference to member 'decode(_:forKey:)'

thattyson
  • 718
  • 8
  • 17
  • OK well now that you've completely changed the question I'd say it's just a duplicate of https://stackoverflow.com/questions/45598461/swift-4-decodable-with-keys-not-known-until-decoding-time (and many others). – matt Jul 30 '19 at 00:22

1 Answers1

8

For your completely changed question, the solution is very similar. Your struct simply adds one additional layer above the array. There's no need for any custom decoding nor even any CodingKeys.

Note that you can't use Any in a Codable.

let json="""
{
"id": "123",
"data": [
    { "<id1>": { "<event>": "2019-05-21T16:15:34-0400" } },
    { "<id2>": { "<event>": "2019-07-01T12:15:34-0400" } },
]
}
"""
struct SampleModel: Codable {
    let id: String
    let data: [[String: [String: Date]]]
}

var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
    let res = try decoder.decode(SampleModel.self, from: json.data(using: .utf8)!)
    print(res)
} catch {
    print(error)
}

The original answer for your original question.

Since you have an array of nested dictionary where none of the dictionary keys are fixed, and since there are no other fields, you can just decode this as a plain array.

Here's an example:

let json="""
[
    { "<id1>": { "<event>": "2019-07-01T12:15:34-0400" } },
    { "<id2>": { "<event>": "2019-05-21T17:15:34-0400" } },
]
"""
var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
    let res = try decoder.decode([[String: [String: Date]]].self, from: json.data(using: .utf8)!)
    print(res)
} catch {
    print(error)
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • I've edited my question to illustrate the reason `Codable` is necessary. Is there a method to decode this way using `KeyedDecodingContainer` within `init(from decoder: Decoder) throws`? – thattyson Jul 30 '19 at 00:03
  • Sigh. You have completely changed your requirements after getting an answer. Give me a few minutes. – rmaddy Jul 30 '19 at 00:06
  • 1
    Isn't the question now just a duplicate of e.g. https://stackoverflow.com/questions/45598461/swift-4-decodable-with-keys-not-known-until-decoding-time ? – matt Jul 30 '19 at 00:19
  • @matt It may very well be that the updated question is now a duplicate of another but I don't think that one applies because the answer here doesn't require any custom `init`. It's a much simpler case. – rmaddy Jul 30 '19 at 00:22
  • @rmaddy thanks for the update! The crux of it was using `Any` in `Codable`; something that now seems obvious after reading your response. – thattyson Jul 30 '19 at 00:29