我的数据结构有一个枚举作为键,我希望下面自动解码.这是一个错误还是一些配置问题?
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与数组混淆了.
typeMismatch(Swift.Array,Swift.DecodingError.Context(codingPath:[Optional(__ lldb_expr_85.AStruct.(CodingKeys in _0E2FD0A9B523101D0DCD67578F72D1DD).dictionary)],debugDescription:"预计会解码数组,但会找到一个字典."))
Ham*_*ish 27
问题是,Dictionary的Codable一致性目前只能正确处理String和Int按键.用于与任何其他一个字典Key类型(如该Key是Encodable/ Decodable),其被编码,并用解码unkeyed的容器(JSON阵列)具有交替的键值.
因此,在尝试解码JSON时:
{"dictionary": {"enumValue": "someString"}}
进入AStruct,"dictionary"键的值应该是一个数组.
所以,
let jsonDict = ["dictionary": ["enumValue", "someString"]]
会工作,产生JSON:
{"dictionary": ["enumValue", "someString"]}
然后将其解码为:
AStruct(dictionary: [AnEnum.enumValue: "someString"])
不过,我真的认为Dictionary的Codable一致性应该能够正确地与任何处理CodingKey符合类型的Key(这AnEnum可以) -因为它可以直接编码和解码成键容器使用该密钥(随意提交Bug请求这个).
在实现之前(如果有的话),我们总是可以构建一个包装器类型来执行此操作:
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")))
为了解决您的问题,您可以使用以下两个 Playground 代码片段之一。
Decodable的init(from:)初始化程序import Foundation
enum AnEnum: String, Codable {
    case enumValue
}
struct AStruct {
    enum CodingKeys: String, CodingKey {
        case dictionary
    }
    enum EnumKeys: String, CodingKey {
        case enumValue
    }
    let dictionary: [AnEnum: String]
}
extension AStruct: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let dictContainer = try container.nestedContainer(keyedBy: EnumKeys.self, forKey: .dictionary)
        var dictionary = [AnEnum: String]()
        for enumKey in dictContainer.allKeys {
            guard let anEnum = AnEnum(rawValue: enumKey.rawValue) else {
                let context = DecodingError.Context(codingPath: [], debugDescription: "Could not parse json key to an AnEnum object")
                throw DecodingError.dataCorrupted(context)
            }
            let value = try dictContainer.decode(String.self, forKey: enumKey)
            dictionary[anEnum] = value
        }
        self.dictionary = dictionary
    }
}
用法:
let jsonString = """
{
  "dictionary" : {
    "enumValue" : "someString"
  }
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let aStruct = try! decoder.decode(AStruct.self, from: data)
dump(aStruct)
/*
 prints:
 ? __lldb_expr_148.AStruct
   ? dictionary: 1 key/value pair
     ? (2 elements)
       - key: __lldb_expr_148.AnEnum.enumValue
       - value: "someString"
 */
KeyedDecodingContainerProtocol的decode(_:forKey:)方法import Foundation
public enum AnEnum: String, Codable {
    case enumValue
}
struct AStruct: Decodable {
    enum CodingKeys: String, CodingKey {
        case dictionary
    }
    let dictionary: [AnEnum: String]
}
public extension KeyedDecodingContainer  {
    public func decode(_ type: [AnEnum: String].Type, forKey key: Key) throws -> [AnEnum: String] {
        let stringDictionary = try self.decode([String: String].self, forKey: key)
        var dictionary = [AnEnum: String]()
        for (key, value) in stringDictionary {
            guard let anEnum = AnEnum(rawValue: key) else {
                let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Could not parse json key to an AnEnum object")
                throw DecodingError.dataCorrupted(context)
            }
            dictionary[anEnum] = value
        }
        return dictionary
    }
}
用法:
let jsonString = """
{
  "dictionary" : {
    "enumValue" : "someString"
  }
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let aStruct = try! decoder.decode(AStruct.self, from: data)
dump(aStruct)
/*
 prints:
 ? __lldb_expr_148.AStruct
   ? dictionary: 1 key/value pair
     ? (2 elements)
       - key: __lldb_expr_148.AnEnum.enumValue
       - value: "someString"
 */
在 Swift 5.6 (Xcode 13.3) SE-0320 CodingKeyRepresentable中已经实现,解决了这个问题。
它增加了对由符合RawRepresentablewithInt和String原始值的枚举键控的字典的隐式支持。
| 归档时间: | 
 | 
| 查看次数: | 11329 次 | 
| 最近记录: |