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

问题描述 投票:0回答:1

因为

[AnyHashable: Any]
(我需要它)我必须实施
init(from:)
encode(to:)
。但是当我运行它时,它无法解码具有数组值的属性:

typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "items", intValue: nil)], debugDescription: "Expected to decode String but found an array instead.", underlyingError: nil))

这是您可以在 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
一切正常。

swift codable
1个回答
0
投票

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

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]
时,您是断言您希望容器专门包含一个包含
String
键和
String
值的字典;然而,
items
键的值是not 一个字符串,而是一个数组。

当你有一个包含任意值的集合时,提取其内容的正确方法是使用键控容器。具体来说,您可以使用带键的容器,其键类型可以采用 any

String
Int
值,例如:

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 中的

values
在您尝试对其进行解码之前不会被断言为任何类型。然后,您可以迭代
untypedContainer.allKeys
(与现在迭代
stringDictionary
相同),并且对于每个键,您可以决定如何从容器中
decode(_:forKey:)
。你可以:

  1. 尝试解码一个
    String
    ,如果你得到一个
    DecodinerError.typeMismatch
    错误,就跳过键值对
  2. 检查每个键的值,如果您有 known 键,则只解码那些
© www.soinside.com 2019 - 2024. All rights reserved.