正在从 Web API 解析 Json

Parsing Json from Web API

你好,我叫 Nico,

我是 App 的完全初学者 programming/SwiftUI。 我正在尝试从网络 api 解析 json 数据,但不知何故我无法正确解析数据。我假设我的 json 结构不正确,但我找不到问题所在。

我从网上得到的 Json API 看起来像这样:

{
 "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": []
}

我的结构如下所示:

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 {

}

我的 Bundle 是这样的:

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

在我看来我正在使用:

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

我得到的错误看起来像这样。

ErrorMessage ConsoleError

在此先感谢您的帮助。

删除您的自定义 Codable 实现和编码密钥。它们不是必需的,编译器可以用这个简单的 JSON 为你生成它们。然后一切都应该工作。

这里的问题具体是你的 APIResponse 解码:

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
}

要解码一个 APIResponse,您尝试解码两个 APIResponse。这将再次尝试解码导致无限递归的 APIResponse。您没有看到的唯一原因是您的 JSON 包含数组,导致对 container(keyedBy:) 的第二次调用失败。尝试在 init(from:) 的第一行设置一个断点(或者如果你必须打印语句),你会看到它在失败之前被调用了第二次。

要解决这个问题,您需要解码属性的实际类型:

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

但正如我所说,您的 JSON 格式中没有任何内容需要手动实现解码器,所以最好让编译器为您合成它。