Writing a SwiftUI model to read from a JSON file with Arrays

Viewed 2612

I'm pretty green when it comes to working with data flow but I'm trying to force myself through the exercise of building an application to learn it. I'm struggling with how to write my model to correctly reference nested keys in a JSON file.

The JSON looks like this:

[{
    "id": 1001,
    "first_name": "Jimmy",
    "last_name": "Simms",
    "cities": [{
            "name": "New York City",
            "towns": [{
                    "name": "Brooklyn"
                },
                {
                    "name": "Manhatten"
                }
            ]
        },
        {
            "name": "Tokyo",
            "towns": [{
                    "name": "Churo"
                },
                {
                    "name": "Riponggi"
                }
            ]
        }
    ]
}]

What I'm unsure how to do is write a model that lets me target nested elements inside the arrays. Broadly speaking, I'm wondering how you might right a SwiftUI model for this JSON set.

This was kind of my stab in the dark at it:

import SwiftUI

struct MyDataModel: Identifiable {
    var id: Int
    var first_name: String
    var last_name: String
    var cities: Array<Any>
    var cities.name: String
    var cities.towns: Array<Any>
    var cities.towns.name: String
}

But my syntax for accessing the nested arrays "Cities" and then the second level "Towns" is just made up and I can find a reference for how to access nested key's like this? Any help would be much appreciated.

1 Answers

Basically you want to create structs for the nested components. The MyDataModel has and array of cities, so that requires an array of City objects. Similarly each City has an array of towns so that requires an array of Town objects.

Assuming that you are accessing the JSON from an api and wanting to convert it into objects, you would need to make it conform to Codable, which is fairly straight forward to do. By convention Swift doesn't use snake_case for variable names, instead it uses camel case, so you can use an enum for the coding keys or you can pass an option in the decoder.

struct MyDataModel: Codable, Identifiable {
    let id: Int
    let firstName: String
    let lastName: String
    let cities: [City]

    enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName = "last_name"
        case cities
    }
}

struct City: Codable {
    let name: String
    let towns: [Town]
}

struct Town: Codable {
    let name: String
}

Update

You could do something like this with it, where myDataModels is an array of your MyDataModel:

var body: some View {
    List {
        ForEach(myDataModels) { model in
            Section(header: Text("\(model.firstName) \(model.lastName)")) {
                ForEach(model.cities, id: \.name) { (city: City) in
                    Section(header: Text(city.name).fontWeight(.bold)) {
                        ForEach(city.towns, id: \.name) { town in
                            Text(town.name)
                        }
                    }
                }
            }
        }
    }
}

Which would look like this:

image of possible layout

Related