从 Web API 解析 Json

Posted

技术标签:

【中文标题】从 Web API 解析 Json【英文标题】:Parsing Json from Web API 【发布时间】:2022-01-08 08:47:33 【问题描述】:

你好,我叫妮可,

我是应用程序编程/SwiftUI 的完全初学者。 我正在尝试从 web api 解析 json 数据,但不知何故我无法正确解析数据。我假设我的 json 结构不正确,但我找不到问题。

我从 Web API 获得的 Json 看起来像这样:


 "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 


我的捆绑包是这样的:

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

提前感谢您的帮助。

【问题讨论】:

您拥有的解码代码是用于解码应用资源文件的内容,而不是从外部API下载和解码。查找使用 URLSession 的教程/文章,以避免您现在拥有的同步代码。至于解码错误,您需要打印实际的错误消息,因此您应该使用trydo/catch 而不是try? 我把你上面的 JSON 转储到app.quicktype.io。忽略所有“助手”功能并提供一个空的InputDataGridstruct,我能够毫无问题地解码发布的 JSON 和 URL 响应 【参考方案1】:

删除您的自定义 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:) 的第一行设置断点(或者如果您必须使用 print 语句),您会看到它在失败之前被第二次调用。

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

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

但正如我所说,你的 JSON 格式没有什么需要手动实现解码器,所以最好是让编译器为你合成它。

【讨论】:

感谢您的快速响应,我尝试了它,现在它正在工作,感谢您的帮助。

以上是关于从 Web API 解析 Json的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2/Web Api - json 解析错误语法错误意外结束输入

在 Swift 中解析 JSON API [关闭]

从 YouTube API 解析 JSON

require.js POST 请求以发现返回“解析 json 错误”的 web api

我无法从 Instagram api 解析 json

快速解析从 API 以 JSON 形式返回的对象