9

Given the following JSON document I'd like to create a struct with four properties: filmCount (Int), year (Int), category (String), and actor (Actor array).

{    
    "filmCount": 5,
    "year": 2018,
    "category": "Other",
    "actors":{  
        "nodes":[  
            {  
                "actor":{  
                    "id":0,
                    "name":"Daniel Craig"
                }
            },
            {  
                "actor":{  
                    "id":1,
                    "name":"Naomie Harris"
                }
            },
            {  
                "actor":{  
                    "id":2,
                    "name":"Rowan Atkinson"
                }
            }
        ]
    }
}

PlacerholderData is a struct storing the three main properties and the list of actors which should be retrieved from the nested nodes container within the actors property from the JSON object.

PlacerholderData:

struct PlaceholderData: Codable {
    let filmCount: Int
    let year: Int
    let category: String
    let actors: [Actor]
}

Actor.swift:

struct Actor: Codable {
    let id: Int
    let name: String
}

I am attempting to do this through providing my own init to initialise the values from the decoder's container manually. How can I go about fixing this without having to have an intermediate struct storing a nodes object?

1
  • You need another structure struct Actors: Codable { let nodes: [Actor] } and use it instead of [Actor] in the PlaceholderData structure: let actors: Actors Commented Mar 22, 2018 at 20:33

1 Answer 1

11

You can use nestedContainer(keyedBy:) and nestedUnkeyedContainer(forKey:) for decoding nested array and dictionary like this to turn it into your desired structure. Your decoding in init(decoder: ) might look something like this,

Actor extension for decoding,

extension Actor: Decodable {

    enum CodingKeys: CodingKey { case id, name }

    enum ActorKey: CodingKey { case actor }

    init(from decoder: Decoder) throws {
        let rootKeys        = try decoder.container(keyedBy: ActorKey.self)
        let actorContainer  = try rootKeys.nestedContainer(keyedBy: CodingKeys.self,
                                                           forKey: .actor)
        try id =  actorContainer.decode(Int.self,
                                       forKey: .id)
        try name =  actorContainer.decode(String.self,
                                         forKey: .name)
    }
}

PlaceholderData extension for decoding,

extension PlaceholderData: Decodable {

    enum CodingKeys: CodingKey { case filmCount, year, category, actors }

    enum NodeKeys: CodingKey { case nodes }

    init(from decoder: Decoder) throws {
        let rootContainer   = try decoder.container(keyedBy: CodingKeys.self)
        try filmCount       =  rootContainer.decode(Int.self,
                                                    forKey: .filmCount)
        try year            =  rootContainer.decode(Int.self,
                                                    forKey: .year)
        try category        =  rootContainer.decode(String.self,
                                                    forKey: .category)
        let actorsNode      = try rootContainer.nestedContainer(keyedBy: NodeKeys.self,
                                                                forKey: .actors)
        var nodes = try actorsNode.nestedUnkeyedContainer(forKey: .nodes)
        var allActors: [Actor] = []

        while !nodes.isAtEnd {
            let actor = try nodes.decode(Actor.self)
            allActors += [actor]
        }
        actors = allActors
    }
}

Then, you can decode it like this,

let decoder = JSONDecoder()
do {
    let placeholder = try decoder.decode(PlaceholderData.self, from: jsonData)
    print(placeholder)
} catch {
    print(error)
}

Here, the basic idea is to decode dictionary container using nestedContainer(keyedBy:) and array container using nestedUnkeyedContainer(forKey:)

Sign up to request clarification or add additional context in comments.

1 Comment

Hi, thanks for the answer although I tried using it and got the next error:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.