首页 > 解决方案 > 使用带有注入属性的 Decodable

问题描述

有没有办法使用Decodable注入的属性?

final class Score: Decodable {
    let value: Int?
    let uniqueId: String

    convenience init(from decoder: Decoder/*, uniqueId: String*/) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        try container.decodeIfPresent(Int.self, forKey: .value).flatMap { value = $0 }
        // self.uniqueId = uniqueId
        [... other properties parsing ...]
    }
}

示例调用:

final class Exam {
    let identifier: Int
    let scores: [Score]
    
    convenience init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        identifier = try container.decode(Int.self, forKey: .identifier)
        scores = try container.decode([Score].self, forKey: .scores)
        // I need to pass Exam's identifier to `score` on init, because it will generate Score's `uniqueId `
        [... other properties parsing ...]
    }
}

这将以 missing 错误结束uniqueId,我需要在 init 之后拥有它,但它不在 JSON 中。由于它是标识符,因此将其设为可选并设置在外部并不是处理它的正确方法。

我很想按照上面评论的方式注入它,但是怎么做呢?

标签: swiftparsingcocoadecodable

解决方案


无法扩展初始化程序,因为它是间接调用的,并且没有提供 API 来扩展它。因此,有几种方法可以绕过它:

  1. userInfo最佳:如果可能,将值注入解码器。
  2. 为响应创建单独的类,为模型创建单独的类。下面的例子。
  3. 使用普通JSONSerialization的而不是Decodable.
  4. 正如@JoakimDanielson 建议的那样,在默认初始化程序中创建随机标识符。问题是它不可重现,因此如果您将其保存到数据库中,您将始终覆盖数据,因为每次解析的 ID 都会不同。

方法 2 的示例:

final class ScoreResponse: Decodable {
    let value: Int?

    convenience init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        try container.decodeIfPresent(Int.self, forKey: .value).flatMap { value = $0 }
        [... other properties parsing ...]
    }
}

final class Score {
    let value: Int?
    let uniqueId: String

    convenience init(from response: ScoreResponse, uniqueId: String) {
        self.value = response.value // etc with other properties
        self.uniqueId = uniqueId
    }
}

final class Exam: Decodable {
    let identifier: String
    let scores: [Score] = []
    
    convenience init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        identifier = try container.decode(String.self, forKey: .identifier)
        try container.decodeIfPresent([ScoreResponse].self, forKey: .scores).forEach {
        scores.append({ Score(from: $0, uniqueId: identifier) })
    }
}

推荐阅读