UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: JSON Model Help

Forums > Swift

Hey everyone,

Long time luker but I have a question that I am sure is simple but having a tough time to find a solution for (probably because I don't know how to phrase it correctly). Basically I have a JSON that is nested but that isnt a huge deal and I have the model working for it at this point. My challenge is the lowest level of the JSON has a few arrays that have just the values in them but not a seperate dictionary key and I am not sure how to model that correctly so I can accsess the data. An example of the dictionary looks like this...

{ "records": [ { "id": "String", "fields": { "Name": "String", "Date Received": "String", "Request Type": [ "String" ], "Email/Phone Number": "String", }, "createdTime": "2021-02-22T16:22:55.000Z" } ] }

Normally I would assume I need to make another struct for the arrayed property but since it only contains the values and strings (when more than one) I don't have a key : value pattern to use for the struct.

3      

First, that's not valid JSON because of this: "Email/Phone Number": "String", (the comma at the end).

Clear that up, and the key indicated by "Request Type" would just have a type of [String].

3      

Sorry you can assume the comma is gone already (Because it is) that was just a bad copy over on my part. And that is the issue there is no key indicated for Request Type. The only thing in the array is the actual values so it reads

requestType : [ "One", "Two", "Three" ] but there isn't a key associated with it.

Here is my structs for the codables if this helps.

struct Response: Codable {
    let records: [Record]
    let offset: String?
}

// MARK: - Record
struct Record: Codable {
    let id: String?
    let fields: Fields
    let createdTime: String?
}

// MARK: - Fields
struct Fields: Codable {
    let dateReceived : String?
    let emailPhoneNumber : String?
    let name : String?
    let requestType : [String]?

    enum CodingKeys: String, CodingKey {
            case dateReceived = "Date Received"
            case emailPhoneNumber = "Email/Phone Number"
            case name = "Name"
            case requestType = "Request Type"
    }
}

The issue I am running into is that while the network call gets the data and it processes and I can see it in the preview. Certain fields like the requestType give me a can not complie in time error that wont resolve unless I don't use them. So am I just accessing them wrong? Right now I am using a ForEach to display the needed data and the general code is Text(index.fields.name ?? "") and it works fine for anything that is not the key : [value] setup in the bottom layer of the JSON.

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

No, you've got it right in your struct. requestType is just an array, so you would access its values like any other array:

if let types = fields.requestType { //since it's an Optional array
    ForEach(types, id: \.self) { t in
        Text(t)
    }
}

Something like that. Point is, it's just an array, so treat it like an array.

3      

Right now this is what I have as my for each

ForEach(results, id: \.id) { index in
                    VStack {
                    //Spacer(minLength: 5)
                    VStack {
                        //Spacer(minLength: 15)
                        Text(index.fields.name ?? "")
                            .font(.system(size: 20, design: .rounded))
                            .fontWeight( .semibold)
                        Text(index.fields.name ?? "Type Of Request")

And name works (that's why it's listed twice right now) but if I change it to

Text(index.fields.requestType ?? "Something")

It won't complie anymore.

3      

Right, because Text requires a String but index.fields.requestType is an array of Strings.

You need an inner ForEach to loop through the requestType array and display each element in its own Text. That's what the example I gave you shows.

3      

So another for each within the stack that would refrence specifically that option?

ForEach(results, id: \.id) { index in
                    VStack {
                    //Spacer(minLength: 5)
                    VStack {
                        //Spacer(minLength: 15)
                        Text(index.fields.name ?? "")
                            .font(.system(size: 20, design: .rounded))
                            .fontWeight( .semibold)
                            ForEach(results, id: \.self) { t in
                              Text(t ?? "Type Of Request")

Or as an if let above the var body: any View { that I refrence in the stack?

3      

Something in the VStack that references the requestType member of the Fields struct. But since requestType is an Optional array, you need to unwrap it first. So, something like this:

ForEach(results, id: \.id) { index in
    VStack {
        //Spacer(minLength: 5)
        VStack {
            //Spacer(minLength: 15)
            Text(index.fields.name ?? "")
                .font(.system(size: 20, design: .rounded))
                .fontWeight( .semibold)
            if let types = index.fields.requestType { //since it's an Optional array
                ForEach(types, id: \.self) { t in
                    Text(t)
                }
            }

3      

Okay.

Well good news is that it works :) so thank you for the help. Now I just need to educate my self more on why lol. It seems like there should be a way to just note it via the struct in a way so that when the decoder is done it is avalible without another for each but hey if it works it works so thank you.

3      

I mean, it's an array. How else would you expect to access its values without looping through them? I suppose if you just wanted to use one value out of the array you could subscript into it like index.field.requestType[0] but you would still have to deal with the optionality of it.

If you know that requestType will always and forever only have one and only one value in it, you could do some custom decoding to convert it to a String rather than [String]? but then you'd also have to deal with all the other properties since custom Decodable implementations are all or nothing.

Or you could add a computed property to your Fields struct to return either the value or an empty String, like so:

var type: String {
    requestType?[0] ?? ""
}

and then in your View:

Text(index.fields.type)

But that would give you an empty Text element taking up space in your UI versus the solution I posted, which removes the Text entirely if it's not needed.

It really all depends on how you would want to handle the case where requestType is nil.

3      

Yeah that all makes sense. I just mean it seems like the decoder should be able to see its an array based on the struct and loop through those values on its own with the network call or first pass using the for each but its all good. I am sure there are very good technical reasons why it does not do that own its own (though it does loop through the array on its own with the other arrays that are higher up in the process) so it's all good and thank you for the help.

In the case of the other values since they are all optional I just added the ?? "If nil text here" so I just kind of assumed that swift would say soemthing like "hey there is no values in the array this time so use the if nil text" like it did with the other string values but as I am still VERY much learning I am fine with just learning how it is designed to work not how I would expect it to work.

3      

I just mean it seems like the decoder should be able to see its an array based on the struct and loop through those values on its own with the network call or first pass using the for each

Just a clarification: The ForEach isn't part of the decoder, it's part of a View. You get JSON from your network call and decode it into your structs then separately use a View struct to display that data. And the View only knows to use a ForEach to loop through an array if you tell it to do so because it is completely divorced from the data structure decoded from the JSON. What is displayed in the View and how it is displayed is totally up to you, the developer, so there is nothing any decoder can do in that regard.

You can create a data structure that conforms to Codable and the source JSON will be decoded into the correct properties and such but that has no connection to your View except what you specify. Just like how you have to tell your View how to handle single data fields like name or emailPhoneNumber (e.g., by placing them in a Text element and giving them particular formatting), you have to tell it how to handle collections of data like the requestType array (e.g., by looping through the values and displaying them in Text elements).

Hope that helps explain why things work the way they do.

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.