Search code examples
arraysswiftcodabledecodablegoogle-books

How to access an container from an array in Decodable (Swift)


I've been trying already sometime, but couldn't find a solution to this problem:

I've the following sample response from the Google Books API:

let json = """
{
    "items": [
        {
        "volumeInfo": {
            "title": "Harry Potter und der Stein der Weisen",
            "description": "Eigentlich hatte Harry geglaubt, er sei ein ganz normaler Junge. Zumindest bis zu seinem elften Geburtstag. Da erfährt er, dass er sich an der Schule für Hexerei und Zauberei einfinden soll. Und warum? Weil Harry ein Zauberer ist. Und so wird für Harry das erste Jahr in der Schule das spannendste, aufregendste und lustigste in seinem Leben. Er stürzt von einem Abenteuer in die nächste ungeheuerliche Geschichte, muss gegen Bestien, Mitschüler und Fabelwesen kämpfen. Da ist es gut, dass er schon Freunde gefunden hat, die ihm im Kampf gegen die dunklen Mächte zur Seite stehen."
        }
        }
    ]
}
""".data(using: .utf8)

This response will never return more than one item. (Specified as a parameter) So it's safe to assume that items only has one member/the first member is the right one.

To represent a book I created this struct:

struct Book: Decodable {

    let title: String
    let description: String?

    enum OuterOuterCodingKeys: CodingKey {
        case items
    }

    enum OuterCodingKeys: CodingKey {
        case volumeInfo
    }

    enum CodingKeys: String, CodingKey {
        case title
        case description
    }

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

Now this is how far I've got. I would now need to somehow extract the items from volumeInfo, but have no idea how I can get in the array.

Thanks for your help!


Solution

  • You're on the right track, but we can give better names to your extra CodingKey enums, and make them private.

    import Foundation
    
    let jsonData = """
    {
        "items": [
            {
            "volumeInfo": {
                "title": "Harry Potter und der Stein der Weisen",
                "description": "Eigentlich hatte Harry geglaubt, er sei ein ganz normaler Junge. Zumindest bis zu seinem elften Geburtstag. Da erfährt er, dass er sich an der Schule für Hexerei und Zauberei einfinden soll. Und warum? Weil Harry ein Zauberer ist. Und so wird für Harry das erste Jahr in der Schule das spannendste, aufregendste und lustigste in seinem Leben. Er stürzt von einem Abenteuer in die nächste ungeheuerliche Geschichte, muss gegen Bestien, Mitschüler und Fabelwesen kämpfen. Da ist es gut, dass er schon Freunde gefunden hat, die ihm im Kampf gegen die dunklen Mächte zur Seite stehen."
            }
            }
        ]
    }
    """.data(using: .utf8)!
    
    struct Book: Decodable {
    
        let title: String
        let description: String?
    
        init(from decoder: Decoder) throws {
            let rootContainer = try decoder.container(keyedBy: RootContainerKeys.self)
            var itemsContainer = try rootContainer.nestedUnkeyedContainer(forKey: .items)
            let itemContainer = try itemsContainer.nestedContainer(keyedBy: ItemKeys.self)
            let myContainer = try itemContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .volumeInfo)
            self.title = try myContainer.decode(String.self, forKey: .title)
            self.description = try myContainer.decodeIfPresent(String.self, forKey: .description)
        }
    
        private enum CodingKeys: String, CodingKey {
            case title
            case description
        }
    
        private enum RootContainerKeys: CodingKey {
            case items
        }
    
        private enum ItemKeys: CodingKey {
            case volumeInfo
        }
    }
    
    let book = try? JSONDecoder().decode(Book.self, from: jsonData)
    print(book)