Search code examples
jsonswiftswift4codable

How to decode a nested JSON struct with Swift Decodable protocol?


Here is my JSON

{
    "id": 1,
    "user": {
        "user_name": "Tester",
        "real_info": {
            "full_name":"Jon Doe"
        }
    },
    "reviews_count": [
        {
            "count": 4
        }
    ]
}

Here is the structure I want it saved to (incomplete)

struct ServerResponse: Decodable {
    var id: String
    var username: String
    var fullName: String
    var reviewCount: Int

    enum CodingKeys: String, CodingKey {
       case id, 
       // How do i get nested values?
    }
}

I have looked at Apple's Documentation on decoding nested structs, but I still do not understand how to do the different levels of the JSON properly. Any help will be much appreciated.


Solution

  • Another approach is to create an intermediate model that closely matches the JSON (with the help of a tool like quicktype.io), let Swift generate the methods to decode it, and then pick off the pieces that you want in your final data model:

    // snake_case to match the JSON and hence no need to write CodingKey enums
    fileprivate struct RawServerResponse: Decodable {
        struct User: Decodable {
            var user_name: String
            var real_info: UserRealInfo
        }
    
        struct UserRealInfo: Decodable {
            var full_name: String
        }
    
        struct Review: Decodable {
            var count: Int
        }
    
        var id: Int
        var user: User
        var reviews_count: [Review]
    }
    
    struct ServerResponse: Decodable {
        var id: String
        var username: String
        var fullName: String
        var reviewCount: Int
    
        init(from decoder: Decoder) throws {
            let rawResponse = try RawServerResponse(from: decoder)
            
            // Now you can pick items that are important to your data model,
            // conveniently decoded into a Swift structure
            id = String(rawResponse.id)
            username = rawResponse.user.user_name
            fullName = rawResponse.user.real_info.full_name
            reviewCount = rawResponse.reviews_count.first!.count
        }
    }
    

    This also allows you to easily iterate through reviews_count, should it contain more than 1 value in the future.