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"
}
}