Home > Enterprise >  SwiftUI decoding JSON from API
SwiftUI decoding JSON from API

Time:02-13

I know there are already some articles regarding this issue, but I could not find anything related to my JSON and it frustrates me to be honest. Therefore, I am searching for help.

This is how my JSON likes like:

{
  "message": {
    "affenpinscher": [],
    "african": [],
    "airedale": [],
    "akita": [],
    "appenzeller": [],
    "australian": [
      "shepherd"
    ],
    "basenji": []
  },
  "status: "succes"
}

So, if I understand it correctly it is dictionary because it starts with {, but what are the things inside the "message"?

This is my Dog.swift class where I am re-writing the JSON, but I am not sure if it is correct:

class Dog: Decodable, Identifiable {
    
    var message: Message?
    var status: String?
}

struct Message: Decodable {
    
    var affenpinscher: [String:[String]]?
    var african: [String]?
    var airedale: [String]?
    var akita: [String]?
    var appenzeller: [String]?
    var australian: [String]?
    var basenji: [String]?
}

As you can see in the first value I was trying to play with data types, but no success.

I am decoding and parsing JSON here:

class ContentModel: ObservableObject {
    
    @Published var dogs = Message()
    
    init() {
        getDogs()
    }
    
    func getDogs(){
        
        // Create URL
        let urlString = Constants.apiUrl
        let url = URL(string: urlString)
        
        if let url = url {
            // Create URL request
            var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 10)
            request.httpMethod = "GET"
             
            // Get URLSession
            let session = URLSession.shared
            
            // Create Data Task
            let dataTask = session.dataTask(with: request) { (data, response, error) in
                // Check that there is not an error
                if error == nil {
                    
                    do {
                        // Parse JSON
                        let decoder = JSONDecoder()
                        let result = try decoder.decode(Dog.self, from: data!)
                        print(result)
                        
                        // Assign result to the dogs property
                        DispatchQueue.main.async {
                            self.dogs = result.message!
                        }
                    } catch {
                        print(error)
                    }
                }
            }
            
            // Start the Data Task
            dataTask.resume()
        }
    }
    
}

And here I would love to iterate through it eventually, which I also have no idea how to do it:

struct ContentView: View {
    
    @EnvironmentObject var model: ContentModel
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVStack {
                    if model.dogs != nil {
//                        ForEach(Array(model.dogs.keys), id: \.self) { d in
//                            Text(d)
//                        }
                    }
                }
                .navigationTitle("All Dogs")
            }
            
        }
    }
}

enter image description here

Thanks in advance for any lead! I have been stuck at this issue for couple of hours already.

Cheers!

CodePudding user response:

First of all don't use classes for a JSON model and to conform to Identifiable you have to add an id property and CodingKeys if there is no key id in the JSON.

My suggestion is to map the unhandy [String: [String]] dictionary to an array of an extra struct

I renamed Dog as Response and named the extra struct Dog

struct Dog {
    let name : String
    let types : [String]
}

struct Response: Decodable, Identifiable {
    
    private enum CodingKeys: String, CodingKey { case message, status }
    let id = UUID()
    
    let dogs: [Dog]
    let status: String
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        status = try container.decode(String.self, forKey: .status)
        let message = try container.decode([String:[String]].self, forKey: .message)
        dogs = message.map(Dog.init).sorted{$0.name < $1.name}
    }
}

In the model declare

@Published var dogs = [Dog]()

and decode

let result = try decoder.decode(Response.self, from: data!)
print(result)

// Assign result to the dogs property
DispatchQueue.main.async {
    self.dogs = result.dogs
}

The dogs array can be displayed seamlessly in a List


PS: Actually appenzeller is supposed to be

"appenzeller": ["sennenhund"],

or correctly in English

 "appenzell": ["mountain dog"],

  • Related