Home > Mobile >  Parsing Json from Web API
Parsing Json from Web API

Time:12-02

Hello my name is Nico,

I am a complete beginner in App programming/SwiftUI. I am trying to parse json data from an web api but somehow I cannot parse the data correctly. I assume that my json structure is not correct but I cannot find the problem.

The Json which I get from the Web API looks something like this:

{
 "pois": [
     {
         "id": "2635094451",
         "lat": "52.410150",
         "lat_s": "52.4",
         "lng": "10.776630",
         "lng_s": "10.8",
         "street": "Röntgenstraße",
         "content": "8137285512",
         "backend": "0-239283152",
         "type": "1",
         "vmax": "50",
         "counter": "0",
         "create_date": "2021-11-18 13:21:50",
         "confirm_date": "2021-11-18 13:21:43",
         "gps_status": "-",
         "info": "             {\"qltyCountryRoad\":1,\"confirmed\":\"0\",\"gesperrt\":\"0\",\"precheck\":\"[Q1|21|0]\"}",
         "polyline": ""
     }
 ],
 "grid": []
}

My Structure looks like this:

struct APIResponse: Codable {
    let pois: [InputDataPois]
    let grid: [InputDataGrid]

    private enum CodingKeys: String, CodingKey {
        case pois
        case grid
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.pois = try container.decode(APIResponse.self, forKey: .pois).pois
        self.grid = try container.decode(APIResponse.self, forKey: .grid).grid
    }
}

struct InputDataPois: Codable, Identifiable {
    let id:String
    let lat:String
    let lat_s:String
    let lng:String
    let lng_s:String
    let street:String
    let content:String
    let backend:String
    let type:String
    let vmax:String
    let counter:String
    let create_date:String
    let confirm_date:String
    let gps_status:String
    let info:String
    let polyline:String
}

extension InputDataPois {
    private enum CodingKeys: String, CodingKey {
        case id
        case lat
        case lat_s
        case lng
        case lng_s
        case street
        case content
        case backend
        case type
        case vmax
        case counter
        case create_date
        case confirm_date
        case gps_status
        case info
        case polyline
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.id = try container.decode(String.self, forKey: .id)
        self.lat = try container.decode(String.self, forKey: .lat)
        self.lat_s = try container.decode(String.self, forKey: .lat_s)
        self.lng = try container.decode(String.self, forKey: .lng)
        self.lng_s = try container.decode(String.self, forKey: .lng_s)
        self.street = try container.decode(String.self, forKey: .street)
        self.content = try container.decode(String.self, forKey: .content)
        self.backend = try container.decode(String.self, forKey: .backend)
        self.type = try container.decode(String.self, forKey: .type)
        self.vmax = try container.decode(String.self, forKey: .vmax)
        self.counter = try container.decode(String.self, forKey: .counter)
        self.create_date = try container.decode(String.self, forKey: .create_date)
        self.confirm_date = try container.decode(String.self, forKey: .confirm_date)
        self.gps_status = try container.decode(String.self, forKey: .gps_status)
        self.info = try container.decode(String.self, forKey: .info)
        self.polyline = try container.decode(String.self, forKey: .polyline)
    }

}

struct InputDataGrid: Codable {

}

and my Bundle like this:

extension Bundle {
    func decode(_ file: String) -> [InputDataPois] {
        // 1. Locate the Json File
        guard let url = URL(string: file) else {
            fatalError("Failed to locate \(file) in bundle")
        }

        // 2.Create a property for the Data
        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to Load \(file) from bundle.")
        }
    
        // 3. Create a property for the data
        let str = String(decoding: data, as: UTF8.self)
        print("\(str)")
    
        guard let loaded = try? JSONDecoder().decode(APIResponse.self, from: data).pois else {
            fatalError("Failed to decode \(file) from bundle.")
        }
        // 4. Return the ready-to-use data
        return loaded
    }
}

And In my View I am using:

let speed: [InputDataPois] = Bundle.main.decode("https://cdn2.atudo.net/api/1.0/vl.php?type=0,1,2,3,4,5,6&box=52.36176390234046,10.588760375976562,52.466468685912744,11.159706115722656")

The error I am getting looks something like this.

ErrorMessage ConsoleError

Thanks in advance for you help.

CodePudding user response:

Remove your custom Codable implementation and coding keys. They are not necessary, the compiler can generate them for you with this simple JSON. Then everything should work.

The problem here specifically is your APIResponse decoding:

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.pois = try container.decode(APIResponse.self, forKey: .pois).pois
    self.grid = try container.decode(APIResponse.self, forKey: .grid).grid
}

To decode an APIResponse you try to decode two APIResponses. This again would try to decode APIResponse leading to endless recursion. The only reason you are not seeing that is that your JSON contains arrays making the second call to container(keyedBy:) fail. Try setting a breakpoint (or if you must a print statement) on the first line of your init(from:) and you will see that this is called a second time before it fails.

To fix that you would need to decode the actual type of your properties:

self.pois = try container.decode([InputDataPois].self, forKey: .pois)

But as I said, there is nothing in your JSON format requiring manually implementing the decoder, so the best is to let the compiler synthesize it for you.

  • Related