Swift4 Decodable-将一半密钥解码为字典

时间:2019-08-05 00:48:06

标签: swift jsondecoder

我遇到的情况是服务器向我发送一个模型,在该模型中我知道某些键的类型和名称,而对其他键则不了解。但是,用户可以编辑自己的其他键值对。

示例:

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

所以我最终要得到一个模型,该模型具有一些声明的属性和一些填充到Dictionary<String, Any>中的值,例如

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

我尝试过类似的事情:

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

但是我收到以下编译时错误:

  

协议类型“任何”都不能符合“可解码”,因为只有具体类型才能符合协议

似乎也无法使用JSONDecoder给我们Data引用原始NSJSONSerialization。因此,我想可以反过来做,首先使用较旧的技术初始化dict,然后使用JSONDecoder初始化模型,并用传递Data的东西替换init,但这感觉很不对劲,因为我们将有效地反序列化两次:/

1 个答案:

答案 0 :(得分:0)

我不同意您将其解析为[String: Any]。 JSON中只有少数合法值类型。距离Any不远。

相反,您的起点IMO将是generic JSON decoder。它将解码为JSON枚举。

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

使用它来解决您的特定问题,您可以执行以下操作(非常接近现有的解码器):

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