web-dev-qa-db-ja.com

Swift 4 Decodable-キーとして列挙型を持つ辞書

私のデータ構造にはキーとして列挙型があり、以下が自動的にデコードされると予想されます。これはバグですか、構成上の問題ですか?

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: "代わりに辞書が見つかりました。"))

24

問題は、 DictionaryCodable適合性 は現在StringおよびIntキーのみを適切に処理できることです。他のKeyタイプ(KeyEncodable/Decodable)の辞書の場合、nkeyedコンテナでエンコードおよびデコードされます。 (JSON配列)交互のキー値。

したがって、JSONをデコードしようとする場合:

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

AStructに変換する場合、"dictionary"キーの値は配列であることが期待されます。

そう、

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

動作し、JSONが生成されます。

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

その後、次のようにデコードされます。

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

しかし、本当にDictionaryCodable適合性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")))
24
Hamish

問題を解決するために、次の2つのPlaygroundコードスニペットのいずれかを使用できます。


#1。 Decodableinit(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"
 */
_

#2。 KeyedDecodingContainerProtocoldecode(_: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"
 */
_
3
Imanou Petit