一尘不染

Swift 4 Decodable-以枚举为键的字典

swift

我的数据结构有一个枚举作为键,我希望以下内容能自动解码。这是错误还是某些配置问题?

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)
}

我得到的错误是,这似乎使字典与数组混淆。

typeMismatch(Swift.Array,Swift.DecodingError.Context(codingPath:[可选(__lldb_expr_85.AStruct。(_
0E2FD0A9B523101D0DCD67578F72D1DD中的CodingKeys)。


阅读 307

收藏
2020-07-07

共1个答案

一尘不染

问题是,DictionaryCodable一致性目前只能正确处理StringInt键。用于与任何其他一个字典Key类型(如该KeyEncodable/
Decodable),其被编码,并用解码 unkeyed的 容器(JSON阵列)具有交替的键值。

因此,当尝试解码JSON时:

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

into中AStruct"dictionary"键的值应为数组。

所以,

let jsonDict = ["dictionary": ["enumValue", "someString"]]

将工作,产生JSON:

{"dictionary": ["enumValue", "someString"]}

然后将其解码为:

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

但是,实际上,我认为DictionaryCodable一致性 应该
能够正确地处理任何CodingKey符合类型KeyAnEnum可以是)–因为它可以使用该密钥进行编码和解码到带密钥的容器中(可以自由地提交请求这个)。

在实施之前(如果有的话),我们始终可以构建包装器类型来执行此操作:

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)
    }
}

(或仅具有dictionarytype属性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")))
2020-07-07