同时从 API 调用和模型文件中解码 Codable 结构

Posted

技术标签:

【中文标题】同时从 API 调用和模型文件中解码 Codable 结构【英文标题】:decode a Codable struct from API call and a mockup file simultaneously 【发布时间】:2019-06-27 05:21:11 【问题描述】:

我目前正在做一个项目,我正在调用一个 Web 服务,该服务返回一个我使用 Codable 解析的 JSON,如下所示:

我的结构:

struct User: Codable 
    var name: String
    var age: Int

API 响应:

"name": "Romiro", "age": 27

解码代码:

let decoded = try! JSONDecoder().decode(User.self, from: data)

我们决定通过添加新字段来扩展 User 信息,如下所示:

struct User: Codable 
    var name: String
    var age: Int
    var detail: Detail


struct Detail: Codable 
    var id: Int 
    var dob: Date 

但是后端还没有开发,所以API响应还是

"name": "Romiro", "age": 27

是否有适当的方法来仅模拟 var detail: Detail 部分,方法是从项目资源中与 Detail 的结构匹配的 detail-mock.json 文件中加载它,但同时保持 API 调用预先存在的User 部分?

通过这样做,我将能够保留调用端点的所有逻辑,并分流唯一正在开发的部分,仍然通过调用

let decoded = try! JSONDecoder().decode(User.self, from: data)

此外,有没有办法在不改变 API 的 json 响应的情况下做到这一点?我不想手动将 detail 部分附加到 je json 响应中。

注意:显然,User 结构是一个例子,在我的项目中这是一个更复杂的结构

【问题讨论】:

用户对象创建后是否要手动添加detail的值? 你可以做这样的事情 var detail: Detail?。可以为 nil 的键或可能不存在于 json 中的键将它们标记为可选 您的意思是var detail: Detail?,然后从文件中解码Detail 并将其设置为我的对象? 【参考方案1】:

您可以在User 上实现自定义解码,如下所示:

struct User: Codable 
    var name: String
    var age: Int
    var detail: Detail

    enum CodingKeys: CodingKey 
        case name, age, detail
    

    init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        age = try container.decode(Int.self, forKey: .age)
        if let detail = try container.decodeIfPresent(Detail.self, forKey: .detail) 
            self.detail = detail
         else 
            let data = try Data(contentsOf: Bundle.main.url(forResource: "mockupDetail", withExtension: "json")!)
            self.detail = try JSONDecoder().decode(Detail.self, from: data)
        
    

注意init 中的if 语句。这就是我决定是从实际的 json 还是从模拟的 json 中读取 detail 的地方。

这样,您不需要将detail 设为可选,但您需要手动解码其他属性。

【讨论】:

【参考方案2】:

首先将detail设置为Detail?类型,即

struct User: Codable 
    var name: String
    var age: Int
    var detail: Detail?

您可以为UserDetail创建2个单独的对象,并将detail对象设置为user.detail,即

do 
    var user = try JSONDecoder().decode(User.self, from: userData)
    let detailData = Data() //replace this with the data obtained from Detail api
    let detail = try JSONDecoder().decode(Detail.self, from: detailData)
    user.detail = detail
 catch  
    print(error)

【讨论】:

这意味着设置var detail: Detail 可选?

以上是关于同时从 API 调用和模型文件中解码 Codable 结构的主要内容,如果未能解决你的问题,请参考以下文章

为 API 调用 Xml2Json 制作 Json 模型

Swift模型中数组和字典的JSON解码

从 api SwiftUI 解码 JSON 文件

SwiftUI 如何从响应 JSON API 中分离单个数据

SwiftUI 结合 Publisher targetstruct 来解码数据

多路RTSP流解码:最高可支持12路视频编解码