Swift4 Decodable - decode half the keys as Dictionary

感情迁移 提交于 2019-12-13 17:42:05

问题


I have the situation where the server sends me a model where i know of types and names of some keys, and don't about the others. However, the user can edit those other key value pairs of their own volition.

Example:

{ "a": "B",
  "b": 42,
  "__customKey1": "customVal1",
  "__customKey2": [41, 42],
  "__customKey3": {"z":"x"}
}

So what i want to end up with is a model with some declared properties and some values stuffed into a Dictionary<String, Any>, e.g.

struct MyStruct {
  var a: String?
  var b: Int?
  var dict: Dictionary<String,Any>
}

I tried something like:

  public struct CodingKeysX: CodingKey {
      public var intValue: Int?
      public var stringValue: String

      public init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
      public init?(stringValue: String) { self.stringValue = stringValue }

      static func make(key: String) -> CodingKeysX {
          return CodingKeysX(stringValue: key)!
      }
  }

 init(from decoder: Decoder) throws {

        let co = try! decoder.container(keyedBy: CodingKeysX.self)

        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.a = try container.decode(String?.self, forKey: .a)
        self.b = try container.decode(Int?.self, forKey: .b)
        let allDeclaredKeys = container.allKeys.map({ $0.stringValue })
        self.dict = Dictionary<String, Any>()
        for key in co.allKeys.filter({ !allDeclaredKeys.contains($0.stringValue) }) {
            self.dict[key.stringValue] = try? co.decodeIfPresent(Any.self, forKey: key)
        }
    }

But I get the following compile-time error:

Protocol type 'Any' cannot conform to 'Decodable' because only concrete types can conform to protocols

It also seems that using JSONDecoder i can't get reference to original Data to us NSJSONSerialization. So i could, i suppose, do it the other way round where i first init the dict using the older technique, then init the model using JSONDecoder and replace init with something passing Data in, but it just feels wrong because we'd be effectively deserializing twice :/


回答1:


I disagree that you should parse this as [String: Any]. There are only a handful of legal value types in JSON. That's nowhere close to Any.

Instead, your starting point IMO would be a generic JSON decoder. That decodes into a JSON enum.

let value = try JSONDecoder().decode(JSON.self, from: json)
==> Optional(["__customKey1": "customVal1", "b": 42, "a": "B", "__customKey2": [41, 42], "__customKey3": ["z": "x"]])

value["a"]?.stringValue   // Note value is a JSON, not a Dictionary
==> Optional("B")

Using that, to solve your specific problem you could do something like this (very close to your existing decoder):

struct MyStruct {
    var a: String?
    var b: Int?
    var dict: [String: JSON] // JSON values, not Any values
}

extension MyStruct: Decodable {

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: JSON.Key.self)

        let knownKeys = [JSON.Key("a"), JSON.Key("b")]

        // Unload the known keys. There's no need for these to be Optional unless
        // they really are optional (and nil is different than ""). The point,
        // is you can do any "normal" validation you want here and throw on error.
        self.a = try container.decodeIfPresent(String.self, forKey: JSON.Key("a"))
        self.b = try container.decodeIfPresent(Int.self, forKey: JSON.Key("b"))

        // Unload the rest into your dictionary
        self.dict = [:]
        for key in container.allKeys where !knownKeys.contains(key) {
            self.dict[key.stringValue] = try container.decode(JSON.self, forKey: key)
        }
    }
}

let ms = try JSONDecoder().decode(MyStruct.self, from: json)
=> MyStruct(a: Optional("B"), b: Optional(42), 
            dict: ["__customKey1": "customVal1", 
                   "__customKey2": [41, 42], 
                   "__customKey3": {"z": "x"}])

ms.dict["__customKey1"]?.stringValue  // Optional("customVal1")


来源:https://stackoverflow.com/questions/57351147/swift4-decodable-decode-half-the-keys-as-dictionary

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!