Swift:自定义编码器/解码器无法解码数组,找到字符串而不是数组

jei2mxaa  于 2023-04-19  发布在  Swift
关注(0)|答案(1)|浏览(137)

由于[AnyHashable: Any](我需要它),我必须实现init(from:)encode(to:)。但当我运行它时,它无法解码数组值的属性:
typeMismatch(Swift.String,Swift.DecodingError.Context(codingPath:[_JSONKey(stringValue:“items”,intValue:nil)],debugDescription:“应解码字符串,但找到的却是数组。",underlyingError:(无))
这是你可以在Playground中运行的代码:

struct ServerResponse: Codable {
    var headers: [AnyHashable: Any]?
    var items: [Item]?
    
    enum CodingKeys: String, CodingKey {
        case items, headers
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: ServerResponse.CodingKeys.self)
        items = try container.decode([Item].self, forKey: ServerResponse.CodingKeys.items)
        let singleValueContainer = try decoder.singleValueContainer()
        let stringDictionary = try singleValueContainer.decode([String: String].self)
        headers = [:]
        for (key, value) in stringDictionary {
            headers?[key] = value
        } 
    }
    
    public func encode(to encoder: Encoder) throws {
        let stringDictionary: [String: String] = Dictionary(
            uniqueKeysWithValues: headers?.map {("\($0)", "\($1)")} ?? []
        )
        var singleValueContainer = encoder.singleValueContainer()
        try singleValueContainer.encode(stringDictionary)
        
        var container = encoder.container(keyedBy: ServerResponse.CodingKeys.self)
        try container.encode(items, forKey: ServerResponse.CodingKeys.items)
    }

    struct Item: Codable {
        let name: String
    }
}

let testData = """
    {
        "items": [
                    {"name": "John"},
                    {"name": "Duo"}
                ]
    }
    """.data(using: .utf8)!
let decoder = JSONDecoder()
do {
    let response = try decoder.decode(ServerResponse.self, from: testData)
    print(response)
} catch {
    print(error)
}

它有什么问题?为什么它抱怨得到String,而我已经把一个数组?如果我从结构体中删除头,并符合Codable一切正常。

vddsk6oq

vddsk6oq1#

这里的问题在于如何尝试从这个顶级字典中提取项目,通过尝试将其解码为字典。具体来说,

let singleValueContainer = try decoder.singleValueContainer()
let stringDictionary = try singleValueContainer.decode([String: String].self)

使用特定的JSON负载,singleValueContainer在这里结束

{
    "items": [ ... ],
    "..." // <- assuming there are other actual keys and values
}

这样做是有效的,但是当你试图将容器的内容解码为[String: String]时,你是在 * Assert * 你期望容器包含一个特定的字典,该字典具有String键和String值;然而,items键的值 * 不是 * 字符串,而是数组。
当你有一个包含任意值的集合时,提取其内容的正确方法是使用带键的容器。具体来说,你可以使用一个带键的容器,其键类型可以接受 * 任何 * StringInt值,如下所示:

struct AnyCodingKey: CodingKey {
    let intValue: Int?
    let stringValue: String

    init?(intValue: Int) {
        self.intValue = intValue
        self.stringValue = "\(intValue)"
    }

    init?(stringValue: String) {
        intValue = Int(stringValue)
        self.stringValue = stringValue
    }
}

使用这种编码键类型,您可以请求decoder作为另一个带键的容器-这一次,键可以是任意的:

let untypedContainer = try decoder.container(keyedBy: AnyCodingKey.self)

诀窍是,现在,untypedContainer中的 * 值 * 不会被Assert为任何类型,直到你尝试解码它们。然后你可以迭代untypedContainer.allKeys(与目前迭代stringDictionary相同),对于每个键,你可以决定如何将decode(_:forKey:)从容器中取出。你可以:
1.尝试解码一个String,如果得到一个DecodinerError.typeMismatch错误,只需跳过键值对即可。
1.检查每个键的值,如果你有已知的键(或者匹配某种模式的键,或者类似的键),那么只解码这些键
例如:

public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: ServerResponse.CodingKeys.self)
    items = try container.decode([Item].self, forKey: ServerResponse.CodingKeys.items)

    headers = [:]
    let untypedContainer = try decoder.container(keyedBy: AnyCodingKey.self)
    for key in untypedContainer.allKeys {
        do {
            let value = try untypedContainer.decode(String.self, forKey: key)
            headers[key.stringValue] = value
        } catch DecodingError.typeMismatch { /* skip this key-value pair */ }
    }
}

相关问题