我的数据结构有一个枚举作为密钥,我希望下面的代码能够自动解码。这是一个bug还是一些配置问题?

import Foundation

enum AnEnum: String, Codable {
  case enumValue
}

struct AStruct: Codable {
  let dictionary: [AnEnum: String]
}

let jsonDict = ["dictionary": ["enumValue": "someString"]]
let data = try! JSONSerialization.data(withJSONObject: jsonDict,     options: .prettyPrinted)
let decoder = JSONDecoder()
do {
  try decoder.decode(AStruct.self, from: data)
} catch {
  print(error)
}

我得到的错误是这个,似乎把dict和数组混淆了。
类型不匹配(swift.array,swift.decodingerror.context(codingpath:
[可选(u lldb_expr_85.astruct.(编码键
_ 0e2fd0a9b52310d0dcd67578f72d1dd).dictionary),debugdescription:“需要解码数组,但找到了字典。”)

最佳答案

问题是Dictionary's Codable conformance目前只能正确处理StringInt键。对于具有任何其他Key类型(其中KeyEncodable/Decodable)的字典,它使用具有交替键值的未知容器(json数组)进行编码和解码。
因此,在尝试解码json时:

{"dictionary": {"enumValue": "someString"}}

AStruct中,"dictionary"键的值应该是一个数组。
所以,
let jsonDict = ["dictionary": ["enumValue", "someString"]]

会起作用,生成json:
{"dictionary": ["enumValue", "someString"]}

然后解码成:
AStruct(dictionary: [AnEnum.enumValue: "someString"])

然而,我真的认为DictionaryCodable一致性应该能够正确地处理任何CodingKey一致性类型,就像它的Key一样(它是AnEnum可以的),因为它可以用那个密钥编码和解码到一个带密钥的容器中(可以请求这个)。
在实现之前(如果有的话),我们始终可以构建一个包装类型来执行以下操作:
struct CodableDictionary<Key : Hashable, Value : Codable> : Codable where Key : CodingKey {

    let decoded: [Key: Value]

    init(_ decoded: [Key: Value]) {
        self.decoded = decoded
    }

    init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: Key.self)

        decoded = Dictionary(uniqueKeysWithValues:
            try container.allKeys.lazy.map {
                (key: $0, value: try container.decode(Value.self, forKey: $0))
            }
        )
    }

    func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: Key.self)

        for (key, value) in decoded {
            try container.encode(value, forKey: key)
        }
    }
}

然后像这样实现:
enum AnEnum : String, CodingKey {
    case enumValue
}

struct AStruct: Codable {

    let dictionary: [AnEnum: String]

    private enum CodingKeys : CodingKey {
        case dictionary
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        dictionary = try container.decode(CodableDictionary.self, forKey: .dictionary).decoded
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(CodableDictionary(dictionary), forKey: .dictionary)
    }
}

(或者使用dictionary类型的CodableDictionary<AnEnum, String>属性并使用自动生成的Codable一致性,然后就dictionary.decoded而言)
现在我们可以按预期解码嵌套的json对象:
let data = """
{"dictionary": {"enumValue": "someString"}}
""".data(using: .utf8)!

let decoder = JSONDecoder()
do {
    let result = try decoder.decode(AStruct.self, from: data)
    print(result)
} catch {
    print(error)
}

// AStruct(dictionary: [AnEnum.enumValue: "someString"])

尽管这么说,但可以说,使用一个键为enum的字典所能实现的只是一个具有可选属性的struct(如果您希望某个给定值始终存在,请将其设为非可选)。
因此,您可能只希望您的模型看起来像:
struct BStruct : Codable {
    var enumValue: String?
}

struct AStruct: Codable {

    private enum CodingKeys : String, CodingKey {
        case bStruct = "dictionary"
    }

    let bStruct: BStruct
}

这对您当前的json很有用:
let data = """
{"dictionary": {"enumValue": "someString"}}
""".data(using: .utf8)!

let decoder = JSONDecoder()
do {
    let result = try decoder.decode(AStruct.self, from: data)
    print(result)
} catch {
    print(error)
}

// AStruct(bStruct: BStruct(enumValue: Optional("someString")))

关于swift - Swift 4 Decodable-以枚举为关键字的字典,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54784007/

10-13 09:33