Swift 4 JSON 使用可配置键解码
Swift 4 JSON decode with configurable keys
我是 Swift 的新手,我需要用一些 可配置键 解析 JSON .
与我在这里看到的许多示例相反,密钥在开始解码操作之前是已知的,它们仅取决于传递给端点的一些参数。
示例:
https://some.provider.com/endpoint/?param=XXX
和
https://some.provider.com/endpoint/?param=YYY
将分别回答:
[
{
"fixed_key1": "value1",
"fixed_key2": "value2",
"variable_key_1_XXX": "some value",
"variable_key_2_XXX": "some other value"
},
...
]
和
[
{
"fixed_key1": "value1",
"fixed_key2": "value2",
"variable_key_1_YYY": "some value",
"variable_key_2_YYY": "some other value"
},
...
]
鉴于这些密钥在解码前是已知的,我希望通过一些巧妙的可解码结构声明and/or CodingKeys,无需写
init(from decoder: Decoder)
很遗憾,我没能做出这样的声明。
当然我不想为每个可能的参数值都写一个Decodable/CodingKeys结构:-)
有什么建议吗?
除非你所有的JSON键都是compile-time常量,否则编译器无法合成解码方法。但是您可以做一些事情来减少手动解码的麻烦。
首先,一些辅助结构和扩展:
/*
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 }
}
然后手动解码:
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)
}
}
}
最后是使用方法:
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)
采用与 类似的方法,您可以通过解码器的 userInfo
字典传递给定的参数信息,然后将其传递给用于从键控容器解码的键类型.
首先,我们可以在 CodingUserInfoKey
上定义一个新的静态成员,用作 userInfo
字典中的键:
extension CodingUserInfoKey {
static let endPointParameter = CodingUserInfoKey(
rawValue: "com.yourapp.endPointParameter"
)!
}
(强制展开永远不会失败;我认为初始化程序是可失败的 as a bug)。
然后我们可以为您的端点参数定义一个类型,再次使用静态成员来抽象底层字符串:
// 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 }
}
然后我们可以定义你的数据模型类型:
struct MyDataModel {
var fixedKey1: String
var fixedKey2: String
var variableKey1: String
var variableKey2: String
}
然后像这样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))
}
}
您可以看到我们在 CodingKeys
上使用静态属性定义固定键,而对于可变键,我们使用将给定参数作为参数的静态方法。
现在您可以像这样执行解码:
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")]
我是 Swift 的新手,我需要用一些 可配置键 解析 JSON . 与我在这里看到的许多示例相反,密钥在开始解码操作之前是已知的,它们仅取决于传递给端点的一些参数。
示例:
https://some.provider.com/endpoint/?param=XXX
和
https://some.provider.com/endpoint/?param=YYY
将分别回答:
[
{
"fixed_key1": "value1",
"fixed_key2": "value2",
"variable_key_1_XXX": "some value",
"variable_key_2_XXX": "some other value"
},
...
]
和
[
{
"fixed_key1": "value1",
"fixed_key2": "value2",
"variable_key_1_YYY": "some value",
"variable_key_2_YYY": "some other value"
},
...
]
鉴于这些密钥在解码前是已知的,我希望通过一些巧妙的可解码结构声明and/or CodingKeys,无需写
init(from decoder: Decoder)
很遗憾,我没能做出这样的声明。
当然我不想为每个可能的参数值都写一个Decodable/CodingKeys结构:-)
有什么建议吗?
除非你所有的JSON键都是compile-time常量,否则编译器无法合成解码方法。但是您可以做一些事情来减少手动解码的麻烦。
首先,一些辅助结构和扩展:
/*
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 }
}
然后手动解码:
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)
}
}
}
最后是使用方法:
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)
采用与 userInfo
字典传递给定的参数信息,然后将其传递给用于从键控容器解码的键类型.
首先,我们可以在 CodingUserInfoKey
上定义一个新的静态成员,用作 userInfo
字典中的键:
extension CodingUserInfoKey {
static let endPointParameter = CodingUserInfoKey(
rawValue: "com.yourapp.endPointParameter"
)!
}
(强制展开永远不会失败;我认为初始化程序是可失败的 as a bug)。
然后我们可以为您的端点参数定义一个类型,再次使用静态成员来抽象底层字符串:
// 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 }
}
然后我们可以定义你的数据模型类型:
struct MyDataModel {
var fixedKey1: String
var fixedKey2: String
var variableKey1: String
var variableKey2: String
}
然后像这样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))
}
}
您可以看到我们在 CodingKeys
上使用静态属性定义固定键,而对于可变键,我们使用将给定参数作为参数的静态方法。
现在您可以像这样执行解码:
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")]