Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS Parsing JSON Using Codable Parsing Different JSON Structures Lists of Objects

So if I wanted to get the id's, titles, etc. from the candidates array from the full JSON, how would I need to do that?

I understand how you would need to get values from the candidates array if that was the full JSON, but how would you go about doing it as the JSON is in this video? I saw him use nestedUnkeyedContainer but it seems he skipped over using that as an example to get values from the candidates array.

1 Answer

Gene Bogdanovich
Gene Bogdanovich
14,618 Points

I also have this problem. So, if this is our JSON:

import Foundation

let json = """
{
    "work": {
        "id": 2422333,
        "popularity": 3.8,
        "sponsor": "Random Publisher, Inc",
        "books_count": 222,
        "ratings_count": 860687,
        "text_reviews_count": 37786,
        "best_book": {
            "id": 375802,
            "title": "Ender's Game (Ender's Saga, #1)",
            "author": {
                "id": 589,
                "name": "Orson Scott Card"
            }
        }
    },

    "candidates": [
            {
                "id": 44687,
                "title": "Enchanters' End Game (The Belgariad, #5)",
                "author": {
                    "id": 8732,
                    "name": "David Eddings"
                }
            },
            {
                "id": 22874150,
                "title": "The End Game",
                "author": {
                    "id": 6876994,
                    "name": "Kate  McCarthy"
                }
            },
            {
                "id": 7734468,
                "title": "Ender's Game: War of Gifts",
                "author": {
                    "id": 236462,
                    "name": "Jake Black"
                }
            }
        ]
}
""".data(using: .utf8)!

And this is the object that we're converting to:

struct SearchResult {
    let id: Int
    let booksCount: Int
    let ratingsCount: Int
    let textReviewsCount: Int
    let bestBook: Book
    let candidates: [Book]


    enum OuterCodingKeys: String, CodingKey {
        case work
        case candidates // Note that I put this here, not in the CodingKeys enum
    }

    enum CodingKeys: String, CodingKey {
        case id
        case booksCount
        case ratingsCount
        case textReviewsCount
        case bestBook
        case popularity
    }
}


struct Author: Codable {
    let id: Int
    let name: String

}

struct Book: Codable {
    let id: Int
    let title: String
    let author: Author
}

I guess it would make sense to put case candidates in the OuterCodingKeys enum since it's on the level of "work", I don't understand why Pasan put it in the CodingKeys enum. But it still doesn't work. I created extra container to parse this array of dictionaries. Try keyword somewhere throws an error.

extension SearchResult: Decodable {
    init(from decoder: Decoder) throws {
        let outerContainer = try decoder.container(keyedBy: OuterCodingKeys.self)
        let innerContainer = try outerContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .work)
        var innerContainerForCandidates = try outerContainer.nestedUnkeyedContainer(forKey: .candidates)

        self.id = try innerContainer.decode(Int.self, forKey: .id)
        self.booksCount = try innerContainer.decode(Int.self, forKey: .booksCount)
        self.ratingsCount = try innerContainer.decode(Int.self, forKey: .ratingsCount)
        self.textReviewsCount = try innerContainer.decode(Int.self, forKey: .textReviewsCount)
        self.bestBook = try innerContainer.decode(Book.self, forKey: .bestBook)



        self.candidates = try innerContainerForCandidates.decode([Book].self)
    }
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(SearchResult.self, from: json)
result.candidates