Home > OS >  SwiftUI why is this API call not working for me?
SwiftUI why is this API call not working for me?

Time:07-25

So I am trying to grab some json Data, decode that and put it in a struct.

import SwiftUI

struct Response : Codable{
    var results: [Result]
}

struct Result: Codable{
    var achievements: Result2

}
struct Result2 : Codable{
    var bedwars_stats : Int
}
VStack{
                    ForEach(results, id: .achievements.bedwars_stats){ item in
                        Text("(item.achievements.bedwars_stats)")

                    }
                }
.task{
            await loadData()
        }

and finally this function:

func loadData() async{
        guard let url = URL(string: url) else{
            print("bad url")
            return
        }
        do {
            let (data, _) = try await URLSession.shared.data(from: url)

            if let decodedRespose = try? JSONDecoder().decode(Response.self, from: data){
                results = decodedRespose.results
            }
        } catch{
            print("bad")
        }
    }

Pic of json And I'm trying to get only bedwars_stats and I just don't fully understand what I'm doing wrong?

CodePudding user response:

according to your bits of json, these are the models you should have:

struct ApiResponse : Codable{
    var success: Bool
    var player: Player
}

struct Player : Codable{
    var _id: String
    var achievements: Achievements
}

struct Achievements : Codable{
    var bedwars_stats: Int?
    var arena_climb_the_ranks: Int?
    // others
}

and decode the server response like this:

 if let decodedRespose = try? JSONDecoder().decode(ApiResponse.self, from: data){
    print("\n---> decodedRespose: \(decodedRespose)")
 }

"...I thought that the name didn't really matter..", yes they do matter a lot, they must match. You can use enum CodingKeys for that as well.

CodePudding user response:

Generally speaking, the best way to 1, make it legible, and two, parse JSON with Codable is to simply follow the name & type of a JSON response in your code. For example, given this JSON, how would your Codable object look?

{
  "array": [
    1,
    2,
    3
  ],
  "boolean": true,
  "null": null,
  "number": 123,
  "object": {
    "a": "b",
    "c": "d",
    "e": "f"
  },
  "string": "Hello World"
}

Your codable, if you follow the type and name, it'll keep things nice and clear.

NOTE your variable names should be the name of your properties. So instead of someArray it would be array because that's the JSON properties name. I didn't do that in my example struct because array, bool, nil, int, etc are all RESERVED names. There is a separate fix for that if it ever comes up, but I don't see that in your case.

struct SampleJsonObject: Codable {
    var someArray: [Int]
    var someBool: Bool
    var someNull: Int?
    var someNum: Int
    var someObj: SomeObject
    var someString: String
}

struct SomeObject: Codable {
    var propA: String
    var propC: String
    var propE: String
}

I want you pay particular attention to the SomeObject which is a separate structure of another JSON object. You can simply think of it as a nested JSON response within the response. I want to point out there's an awesome website, jsonblob.com, that allows you to paste your JSON into it, then "Beautify" it so that it'll be easier to read. It'll also make it much easier to determine the difference between an Array vs Object or [] and {} in JSON. I see that most often tripping people up. Finally to parse it out, all you need to do is use the code you're already using. In my example, it'll look something like this. Obviously you'll still need to implement the try catch block but this should make it much easier, provided you follow the pattern of the original JSON response.

 if let decodedRespose = try? JSONDecoder().decode(SampleJsonObject.self, from: data){
                results = decodedRespose.results

So given your actual object, that you pasted.

{
    "success": true,
    "player": {
        "_id": "asdfasdf131413414",
        "achievements": {
            "b": 10,
            "bedwars_stats": 1034
        }
    }
}

Your code would look like this for your Codable object.

struct Response: Codable {
    var success: Bool
    var player: Player
}

struct Player: Codable {
    var _id: String 
    var achievements: Achievement
}

struct Achievement: Codable {
    var b: Int
    var bedwars_stats: Int
}

Which you would decode directly into your Response object and it'll take care of the structure for the remainder. What do you do if you want to re-name your variables because either they don't look right, or they're a reserved name? Well the solution to that is CodingKeys which is relatively easy to implement. Suppose we want bedwars_stats to be stats how would we implement that?

struct Achievement: Codable {
     //All your other properties
     //This one is your "Renamed" key.
     var stats: Int

    enum CodingKeys: String, CodingKey {
        case stats = "bedwars_stats"
    }
}
  • Related