【问题标题】:decoder.container(keyedBy:) throws DecodingError.typeMismatch error. Codable bug?decoder.container(keyedBy:) 抛出 DecodingError.typeMismatch 错误。可编码的错误?
【发布时间】:2017-11-27 19:13:30
【问题描述】:

我正在使用Codable 协议从 Web API 解码 JSON。我的这个 API 的Swift 数据模型包括类继承(子类)和组合(对象作为其他对象的属性)。在 JSON 中,相同的属性名称可能代表一个完整的对象,或者表示该对象在数据库中的 id 的单个字符串。

据我所知,使用Codable 处理这种JSON 的唯一模式是在对象的初始化程序init(from decoder: Decoder) 中“手动”进行解码,并首先尝试解码整个对象。如果失败(通过抛出必须捕获的错误),然后重试解码与 String 相同的属性。

只要包含 varient 属性的对象不是另一个 Decodable 类的子类,这很有效。在这种情况下,解码基类的属性将在调用函数decoder.container(keyedBy:) 时抛出错误DecodingError.typeMismatch

请参阅下面的示例代码。

这是一个已知的错误吗?和/或在这种情况下我是否错过了另一种解码方法?

顺便说一句,如果在引发 DecodingError.typeMismatch 错误之后调用 decoder.container(keyedBy:),即使该错误已被捕获,也会在单个函数中引发相同的错误。

import Foundation

// A `Base` class
class Base: Codable {
    var baseProperty: String? = "baseProperty"
    init() {}

    private enum CodingKeys: String, CodingKey { case baseProperty }

    required init(from decoder: Decoder) throws {
//===>> The next line will throw DecodingError.typeMismatch
        let container = try decoder.container(keyedBy: CodingKeys.self)
        baseProperty = try container.decode(String.self, forKey: .baseProperty)
    }
}

// A Subclass of `Base`
class Sub: Base {
    // An `Other` class which is a property of the `Sub` class
    class Other: Codable { var id: String? = "otherID" }
    var subProperty: Other? = nil
    override init() { super.init() }

    private enum CodingKeys: String, CodingKey { case subProperty }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do { subProperty = try container.decode(Other.self,
                                                forKey: .subProperty)
        }
        catch { // We didn't find a whole `Other` object in the JSON; look for a `String` instead
            let s = try container.decode(String.self, forKey: .subProperty)
            subProperty = Other()
            subProperty?.id = s
        }
        try super.init(from: decoder)
    }
}

// Some sample JSON data:
let json = """
{"baseProperty" : "baseProperty",
 "subProperty" : "someIDString"}
""".data(using: .utf8)!

// MAIN program -----------------------------------------------------
// Decode the JSON to produce a new Sub class instance
do {
    _ = try JSONDecoder().decode(Sub.self, from: json)
}
catch DecodingError.typeMismatch( _, let context) {
    print("DecodingError.typeMismatch: \(context.debugDescription)")
    print("DecodingError.Context: codingPath:")
    for i in 0..<context.codingPath.count { print("  [\(i)] = \(context.codingPath[i])") }
}

【问题讨论】:

    标签: json swift cocoa codable


    【解决方案1】:

    这个is a known bug,已在this pull request 中修复(并将使其成为Swift 4.1)。

    问题基本上是当Other 的解码失败时,解码器会忘记从其内部堆栈中弹出其容器,因此意味着任何未来的解码start 在嵌套容器中.subProperty;因此,为什么在尝试从那里解码对象时会出现类型不匹配错误(因为只有一个字符串!)。

    在修复之前,一种解决方法是不使用decode(_:forKey:);获得一个超级解码器,然后尝试从中解码Other

    所以替换这个:

    subProperty = try container.decode(Other.self, forKey: .subProperty)
    

    用这个:

    let subPropertyDecoder = try container.superDecoder(forKey: .subProperty)
    subProperty = try Other(from: subPropertyDecoder)
    

    这是可行的,因为现在我们有了一个全新的解码器实例,我们不能破坏主解码器的堆栈。

    【讨论】:

    • 天哪,我欠你一杯啤酒。 ;-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 1970-01-01
    • 2021-07-23
    • 1970-01-01
    • 2012-10-28
    相关资源
    最近更新 更多