Swift Json how to decode with no top level key and autogenerated keys

Viewed 643

This one does not help me: Swift 4 parsing json numeric keys with 1+n amount

The case is different.

I have this JSON (simplified a bit):

{
        "1": {
            "name": "Tea room",
            "description": "A room for tea"
        },
        "2": {
            "name": "Playground",
            "description": "Here you can play all day long"
        },
        "3": {
            "name": "Kitchen",
            "description": "Hungry persons can go here"
        }
    }

My problem is how to decode this. I am using Swift version 5. And I am trying to use JSONDecoder().decode();

The keys "1", "2" and "3" are actually Ids and are generated by the API. I don't know how many rooms I will receive. And I don't know what key they will have, but I need to know the key (the Id) to be able to go further.

I am working on something like this:

struct Rooms: Decodable {
  let id: Int; //THIS ONE IS MY PROBLEM
  let rooms: [Room]?;
}

struct Room: Decodable {
    let name: String?;
    let description: String?;
        
    private enum CodingKeys: String, CodingKey {
        case name, description;
    }
}

//Here trying to decode the JSON I got back.
JSONDecoder().decode([Group].self, from: jsonResult);

This is giving me a headache :)

Can anyone please help me in the right direction?

3 Answers

You can decode this as dictionary: [String: Room]:

JSONDecoder().decode([String: Room].self, from: jsonResult);

Given this model (CodingKeys doesn't needed if property names have the same name as your JSON keys):

struct Room: Decodable {
    let name: String?
    let description: String?
}

You can decode this JSON to [String: Room] type:

let decoder = JSONDecoder()

do {
    let decoded = try decoder.decode([String: Room].self, from: jsonResult)
    print(decoded)
} catch {
    print(error)
}

Since you need to have the id it should be a property of Room

struct Room: Decodable {
    var id: Int?
    let name: String
    let description: String
}

With this we can decode the json as a dictionary of [String: Room] and use map to assign the right id to each room

do {
    let dictionary = try JSONDecoder().decode([String: Room].self, from: data)
    let rooms = dictionary.map { tuple -> Room in
        var room = tuple.value
        room.id = Int(tuple.key)
        return room
    }
    print(rooms)
} catch {
    print(error)
}

If you don't want to make id optional you can decode it as a dictionary of dictionaries and create Room object when mapping

do {
    let dictionary = try JSONDecoder().decode([String: [String: String]].self, from: data)
    let rooms = dictionary.compactMap { tuple -> Room? in
        guard let id = Int(tuple.key), let name = tuple.value["name"], let description = tuple.value["description"] else {
            return nil
        }
        return Room(id: id, name: name, description: description)
    }
    print(rooms)
} catch {
    print(error)
}
Related