简体   繁体   English

带可配置键的Swift 4 JSON解码

[英]Swift 4 JSON decode with configurable keys

I'm new to Swift and I need to parse a JSON with some configurable keys . 我是Swift的新手,我需要使用一些可配置的密钥解析JSON。 Opposite to many examples I've seen here, the keys are known before the decode operation is started, they just depend on some parameters passed to endpoint. 与我在这里看到的许多例子相反,密钥在解码操作开始之前就已知,它们只依赖于传递给端点的一些参数。

Example: 例:

https://some.provider.com/endpoint/?param=XXX

and

https://some.provider.com/endpoint/?param=YYY

will answer, respectively: 将分别回答:

[
    {
        "fixed_key1": "value1",
        "fixed_key2": "value2",
        "variable_key_1_XXX": "some value",
        "variable_key_2_XXX": "some other value"
    },
    ...
]      

and

[
    {
        "fixed_key1": "value1",
        "fixed_key2": "value2",
        "variable_key_1_YYY": "some value",
        "variable_key_2_YYY": "some other value"
    },
    ...
]  

Given that those keys are known before decoding , I was hoping to get away with some clever declaration of a Decodable structure and/or CodingKeys , without the need to write the 鉴于这些密钥在解码之前是已知的,我希望能够通过一些巧妙的Decodable结构和/或CodingKeys声明 ,而无需编写

init(from decoder: Decoder)

Unfortunately, I was not able to come up with such a declaration. 不幸的是,我无法提出这样的声明。

Of course I don't want to write one Decodable/CodingKeys structure for every possible parameter value :-) 当然我不想为每个可能的参数值编写一个Decodable / CodingKeys结构:-)

Any suggestion ? 有什么建议吗?

Unless all your JSON keys are compile-time constants, the compiler can't synthesize the decoding methods. 除非所有JSON键都是编译时常量,否则编译器无法合成解码方法。 But there are a few things you can do to make manual decoding a lot less cumbersome. 但是,您可以采取一些措施来减轻手动解码的麻烦。

First, some helper structs and extensions: 首先,一些辅助结构和扩展:

/*
Allow us to initialize a `CodingUserInfoKey` with a `String` so that we can write:
    decoder.userInfo = ["param": "XXX"]

Instead of:
    decoder.userInfo = [CodingUserInfoKey(rawValue:"param")!: "XXX"]
*/
extension CodingUserInfoKey: ExpressibleByStringLiteral {
    public typealias StringLiteralType = String

    public init(stringLiteral value: StringLiteralType) {
        self.rawValue = value
    }
}

/*
This struct is a plain-vanilla implementation of the `CodingKey` protocol. Adding
`ExpressibleByStringLiteral` allows us to initialize a new instance of
`GenericCodingKeys` with a `String` literal, for example:
    try container.decode(String.self, forKey: "fixed_key1")

Instead of:
    try container.decode(String.self, forKey: GenericCodingKeys(stringValue: "fixed_key1")!)
*/
struct GenericCodingKeys: CodingKey, ExpressibleByStringLiteral {
    // MARK: CodingKey
    var stringValue: String
    var intValue: Int?

    init?(stringValue: String) { self.stringValue = stringValue }
    init?(intValue: Int) { return nil }

    // MARK: ExpressibleByStringLiteral
    typealias StringLiteralType = String
    init(stringLiteral: StringLiteralType) { self.stringValue = stringLiteral }
}

Then the manual decoding: 然后手动解码:

struct MyDataModel: Decodable {
    var fixedKey1: String
    var fixedKey2: String
    var variableKey1: String
    var variableKey2: String

    enum DecodingError: Error {
        case missingParamKey
        case unrecognizedParamValue(String)
    }

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

        // Decode the fixed keys
        self.fixedKey1 = try container.decode(String.self, forKey: "fixed_key1")
        self.fixedKey2 = try container.decode(String.self, forKey: "fixed_key2")

        // Now decode the variable keys
        guard let paramValue = decoder.userInfo["param"] as? String else {
            throw DecodingError.missingParamKey
        }

        switch paramValue {
        case "XXX":
            self.variableKey1 = try container.decode(String.self, forKey: "variable_key_1_XXX")
            self.variableKey2 = try container.decode(String.self, forKey: "variable_key_2_XXX")
        case "YYY":
            self.variableKey1 = try container.decode(String.self, forKey: "variable_key_1_YYY")
            self.variableKey2 = try container.decode(String.self, forKey: "variable_key_2_YYY")
        default:
            throw DecodingError.unrecognizedParamValue(paramValue)
        }
    }
}

And finally here's how you use it: 最后这是你如何使用它:

let jsonData = """
[
    {
        "fixed_key1": "value1",
        "fixed_key2": "value2",
        "variable_key_1_XXX": "some value",
        "variable_key_2_XXX": "some other value"
    }
]
""".data(using: .utf8)!

// Supplying the `userInfo` dictionary is how you "configure" the JSON-decoding 
let decoder = JSONDecoder()
decoder.userInfo = ["param": "XXX"]
let model = try decoder.decode([MyDataModel].self, from: jsonData)

print(model)

Taking a similar approach to @Code Different's answer , you can pass the given parameter information through the decoder's userInfo dictionary, and then pass this onto the key type that you use to decode from the keyed container. @Code Different的答案采用类似的方法,您可以通过解码器的userInfo字典传递给定的参数信息,然后将其传递给您用来从密钥容器解码的密钥类型。

First, we can define a new static member on CodingUserInfoKey to use as the key in the userInfo dictionary: 首先,我们可以在CodingUserInfoKey上定义一个新的静态成员,以用作userInfo字典中的键:

extension CodingUserInfoKey {
  static let endPointParameter = CodingUserInfoKey(
    rawValue: "com.yourapp.endPointParameter"
  )!
}

(the force unwrap never fails; I regard the fact the initialiser is failable as a bug ). (力量展开永远不会失败;我认为初始化器可以作为一个错误使用 )。

Then we can define a type for your endpoint parameter, again using static members to abstract away the underlying strings: 然后我们可以为您的端点参数定义一个类型,再次使用静态成员抽象出底层字符串:

// You'll probably want to rename this to something more appropriate for your use case
// (same for the .endPointParameter CodingUserInfoKey).
struct EndpointParameter {

  static let xxx = EndpointParameter("XXX")
  static let yyy = EndpointParameter("YYY")
  // ...

  var stringValue: String
  init(_ stringValue: String) { self.stringValue = stringValue }
}

Then we can define your data model type: 然后我们可以定义您的数据模型类型:

struct MyDataModel {
  var fixedKey1: String
  var fixedKey2: String
  var variableKey1: String
  var variableKey2: String
}

And then make it Decodable like so: 然后像它这样使它可Decodable

extension MyDataModel : Decodable {

  private struct CodingKeys : CodingKey {
    static let fixedKey1 = CodingKeys("fixed_key1")
    static let fixedKey2 = CodingKeys("fixed_key2")

    static func variableKey1(_ param: EndpointParameter) -> CodingKeys {
      return CodingKeys("variable_key_1_\(param.stringValue)")
    }
    static func variableKey2(_ param: EndpointParameter) -> CodingKeys {
      return CodingKeys("variable_key_2_\(param.stringValue)")
    }

    // We're decoding an object, so only accept String keys.
    var stringValue: String
    var intValue: Int? { return nil }
    init?(intValue: Int) { return nil }
    init(stringValue: String) { self.stringValue = stringValue }
    init(_ stringValue: String) { self.stringValue = stringValue }
  }

  init(from decoder: Decoder) throws {
    guard let param = decoder.userInfo[.endPointParameter] as? EndpointParameter else {
      // Feel free to make this a more detailed error.
      struct EndpointParameterNotSetError : Error {}
      throw EndpointParameterNotSetError()
    }

    let container = try decoder.container(keyedBy: CodingKeys.self)

    self.fixedKey1 = try container.decode(String.self, forKey: .fixedKey1)
    self.fixedKey2 = try container.decode(String.self, forKey: .fixedKey2)
    self.variableKey1 = try container.decode(String.self, forKey: .variableKey1(param))
    self.variableKey2 = try container.decode(String.self, forKey: .variableKey2(param))
  }
}

You can see we're defining the fixed keys using static properties on CodingKeys , and for the variable keys we're using static methods that take the given parameter as an argument. 您可以看到我们使用CodingKeys上的静态属性定义固定键,对于变量键,我们使用将给定参数作为参数的静态方法。

Now you can perform a decode like so: 现在您可以像这样执行解码:

let jsonString = """
[
  {
    "fixed_key1": "value1",
    "fixed_key2": "value2",
    "variable_key_1_XXX": "some value",
    "variable_key_2_XXX": "some other value"
  }
]
"""

let decoder = JSONDecoder()
decoder.userInfo[.endPointParameter] = EndpointParameter.xxx
do {
  let model = try decoder.decode([MyDataModel].self, from: Data(jsonString.utf8))
  print(model)
} catch {
  print(error)
}

// [MyDataModel(fixedKey1: "foo", fixedKey2: "bar",
//              variableKey1: "baz", variableKey2: "qux")]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM