'How to decode dictionary JSON response in Swift?

struct Chat: Codable, Identifiable {
    let id =  UUID()
    var Messages: [Messages]
}


class ChatApi : ObservableObject{
    @Published var chats = Chat()
    
    func loadData(completion:@escaping (Chat) -> ()) {
        let urlString = prefixUrl+"/room"
        let url = NSURL(string: urlString as String)!
        var request = URLRequest(url: url as URL)

        request.setValue(accessKey, forHTTPHeaderField: "X-Access-Key-Id")
        request.setValue(secretkey, forHTTPHeaderField: "X-Access-Key-Secret")
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            let chats = try! JSONDecoder().decode(Chat.self, from: data!)
            print(chats)
            DispatchQueue.main.async {
                completion(chats)
            }
        }.resume()
        
    }
}

I'm not able to decode the following JSON response using Swift.

{
    "Messages": [
        {...}
    ]
}

I have tried the above ways and Xcode keeps throwing error. Although I'm able to decode JSON response with another function that are like this

[
    {...},
    {...},
    {...}
]

I'm able to decode JSON response that are returned as arrays but not as dictionaries.

Example response to decode

{
    "Messages": [
        {
            "_id": "MS4mMbTXok8g",
            "_created_at": "2022-04-05T10:58:54Z",
            "_created_by": {
                "_id": "Us123",
                "Name": "John Doe",
            },
            "_modified_at": "2022-04-05T10:58:54Z",
            "Type": "Message",
            "_raw_content": "ss",
            "RoomId": "Ro1234",
        },
        {
            "_id": "MS4m3oYXadUV",
            "_created_at": "2022-04-04T15:22:21Z",
            "_created_by": {
                "_id": "Us678",
                "Name": "Jim Lane",
            },
            "_modified_at": "2022-04-04T15:22:21Z",
            "Type": "Message",
            "_raw_content": "ss",
            "RoomId": "Ro1234",
        }
    ]
}

The data model that I've used is

struct CreatedBy: Codable {
    var _id: String
    var Name: String
}    
struct Messages: Codable {
    var _id: String
    var _created_by: CreatedBy?
    var `Type`: String?
    var _raw_content: String
}

struct Chat: Codable, Identifiable {
    let id =  UUID()
    var Messages: [Messages]
}

The error message before compilation is Editor placeholder in source file



Solution 1:[1]

I am going to introduce you to a couple of sites that will help when handling JSON decoding: JSON Formatter & Validator and Quicktype. The first makes sure that the JSON that you are working off of is actually valid, and will format it into a human readable form. The second will write that actual decodable structs. These are not perfect, and you may want to edit them, but they will get the job done while you are learning.

As soon as you posted your data model, I could see the problem. One of your variables is:

var `Type`: String?

The compiler is seeing the ` and thinking it is supposed to be a placeholder. You can't use them in the code.

Also, though there is not any code posted, I am not sure you need to make Chat Identifiable, as opposed to Messages which could be, but are not. I would switch, or at least add, Identifiable to Messages. I also made CreatedBy Identifiable since it also has a unique id.

The other thing you are missing which will make your code more readable is a CodingKeys enum. This translates the keys from what you are getting in JSON to what you want your variables to actually be. I ran your JSON through the above sites, and this is what came up with:

// MARK: - Chat
struct Chat: Codable {
    let messages: [Message]

    enum CodingKeys: String, CodingKey {
        case messages = "Messages"
    }
}

// MARK: - Message
struct Message: Codable, Identifiable {
    let id: String
    let createdAt: Date
    let createdBy: CreatedBy
    let modifiedAt: Date
    let type, rawContent, roomID: String

    enum CodingKeys: String, CodingKey {
        case id = "_id"
        case createdAt = "_created_at"
        case createdBy = "_created_by"
        case modifiedAt = "_modified_at"
        case type = "Type"
        case rawContent = "_raw_content"
        case roomID = "RoomId"
    }
}

// MARK: - CreatedBy
struct CreatedBy: Codable, Identifiable {
    let id, name: String

    enum CodingKeys: String, CodingKey {
        case id = "_id"
        case name = "Name"
    }
}

This gives you conventional Swift variables, as opposed to the snake case the JSON is giving you. Try this in your code and let us know if you have any more problems.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Yrb