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

时间:2019-06-27 05:21:11

标签: swift codable jsondecoder

我目前正在一个项目上,我正在调用一个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-mock.json文件中加载文件,是否有一种仅模拟Detail部分的正确方法,但与此同时保留对先前存在的User部分的API调用?

这样做,我将能够保留调用端点的所有逻辑,并通过调用

来分流正在开发的唯一部分。
let decoded = try! JSONDecoder().decode(User.self, from: data)

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

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

2 个答案:

答案 0 :(得分:3)

您可以在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)
        }
    }
}

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

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

答案 1 :(得分:1)

首先将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)
}