私のデータ構造にはキーとして列挙型があり、以下が自動的にデコードされると予想されます。これはバグですか、構成上の問題ですか?
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:[Optional(__ lldb_expr_85.AStruct。(_ 0E2FD0A9B523101D0DCD67578F72D1DDのCodingKeys).dictionary)]、debugDescription: "代わりに辞書が見つかりました。"))
問題は、 Dictionary
のCodable
適合性 は現在String
およびInt
キーのみを適切に処理できることです。他のKey
タイプ(Key
がEncodable
/Decodable
)の辞書の場合、nkeyedコンテナでエンコードおよびデコードされます。 (JSON配列)交互のキー値。
したがって、JSONをデコードしようとする場合:
{"dictionary": {"enumValue": "someString"}}
AStruct
に変換する場合、"dictionary"
キーの値は配列であることが期待されます。
そう、
let jsonDict = ["dictionary": ["enumValue", "someString"]]
動作し、JSONが生成されます。
{"dictionary": ["enumValue", "someString"]}
その後、次のようにデコードされます。
AStruct(dictionary: [AnEnum.enumValue: "someString"])
しかし、本当にDictionary
のCodable
適合性shouldは、CodingKey
適合型をKey
(which 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)
}
}
(または、タイプCodableDictionary<AnEnum, String>
のdictionary
プロパティを持ち、自動生成された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")))
問題を解決するために、次の2つの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"
*/
_