在 Swift 中从多个服务解码 JSON 的更简单方法



前提

我有一个符合Decodablestruct,因此它可以通过init(from:)从各种响应中解码JSON。对于我希望解码的每种类型的 JSON 响应,我都有一个符合CodingKeyenum

下面是一个简化的示例,可以放入 Swift 游乐场:

import Foundation
// MARK: - Services -
struct Service1 {}
struct Service2 {}
// MARK: - Person Model -
struct Person {
let name: String
}
extension Person: Decodable {
enum CodingKeys: String, CodingKey {
case name = "name"
}
enum Service2CodingKeys: String, CodingKey {
case name = "person_name"
}
// And so on through service n...
init(from decoder: Decoder) throws {
switch decoder.userInfo[.service] {
case is Service1.Type:
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
case is Service2.Type:
let container = try decoder.container(keyedBy: Service2CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
// And so on through service n...
default:
fatalError("Missing implementation for service.")
}
}
}
// MARK: - CodingUserInfoKey -
extension CodingUserInfoKey {
static let service = CodingUserInfoKey(rawValue: "service")!
}
// MARK: - Responses -
// The JSON response from service 1.
let service1JSONResponse = """
[
{
"name": "Peter",
}
]
""".data(using: .utf8)!
// The JSON response from service 2.
let service2JSONResponse = """
[
{
"person_name": "Paul",
}
]
""".data(using: .utf8)!
// And so on through service n... where other services have JSON responses with keys of varied names ("full_name", "personName").
// MARK: - Decoding -
let decoder = JSONDecoder()
decoder.userInfo[.service] = Service1.self
let service1Persons = try decoder.decode([Person].self, from: service1JSONResponse)
decoder.userInfo[.service] = Service2.self
let service2Persons = try decoder.decode([Person].self, from: service2JSONResponse)

问题

我遇到的问题是,我需要解码许多不同的服务来解码响应,并且模型具有比这个简化示例更多的属性。随着服务数量的增加,解码这些响应所需的案例数量也会增加。

问题

如何简化init(from:)实现以减少所有这些代码重复?

尝试

我尝试为每个服务存储正确的CodingKey.Type并将其传递到container(keyedBy:),但是出现此错误:

无法使用类型为"(keyedBy: CodingKey.Type("的参数列表调用"容器"。

init(from decoder: Decoder) throws {
let codingKeyType: CodingKey.Type
switch decoder.userInfo[.service] {
case is Service1.Type: codingKeyType = CodingKeys.self
case is Service2.Type: codingKeyType = Service2CodingKeys.self
default: fatalError("Missing implementation for service.")
}
let container = try decoder.container(keyedBy: codingKeyType) // ← Error
name = try container.decode(String.self, forKey: .name)
}

与其尝试使用CodingKeys和日益复杂的init来解决这个问题,我建议通过协议来编写它:

protocol PersonLoader: Decodable {
var name: String { get }
// additional properties
}
extension Person {
init(loader: PersonLoader) {
self.name = loader.name
// additional properties, but this is one-time
}
}

或者,特别是如果 Person 是只读的简单数据对象,您只需将 Person 设置为协议,然后就可以避免此额外的复制步骤。

然后,您可以单独定义每个服务的接口:

struct Service1Person: PersonLoader {
let name: String
}
struct Service2Person: PersonLoader {
let person_name: String
var name: String { person_name }
}

完成后映射到"人员":

let service2Persons = try decoder.decode([Service2Person].self,
from: service2JSONResponse)
.map(Person.init)

如果您使用仅协议的方法,它将如下所示:

protocol Person: Decodable {
var name: String { get }
// additional properties
}
struct Service1Person: Person {
let name: String
}
struct Service2Person: Person {
var name: String { person_name }
let person_name: String
}
let service2Personsx = try decoder.decode([Service2Person].self,
from: service2JSONResponse) as [Person]

Person的 init(from:( 中没有一堆自定义的每服务(或每服务类型(功能,我认为这很难做到。不能将自定义CodingKey符合枚举的枚举传递到decoder.container(keyedBy:)中,因为它对该枚举的类型是通用的。

一种方法是使用自定义密钥解码策略,并从字典或通过自定义密钥解码方法/闭包中的函数执行映射。

在下面的示例中,我使用枚举来表示服务。映射字典与枚举大小写无关,因此反映了服务/服务类型键映射。希望这可以作为更复杂的实际用例的有用路线图。

import Foundation
// MARK: - Custom Key Decoding -
struct MyCodingKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
// MARK: - Services -
enum Services: String {
case service1
case service2
}
extension Services {
var mapping: [String:String] {
switch self {
case .service1: return [:]
case .service2: return ["person_name": "name"]
}
}
func getPersons(jsonData: Data) throws -> [Person] {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom { (keys: [CodingKey]) -> CodingKey in
let lastKey = keys.last!
guard lastKey.intValue == nil else {
return MyCodingKey(intValue: lastKey.intValue!)!
}
guard let stringValue = self.mapping[lastKey.stringValue] else {
return lastKey
}
return MyCodingKey(stringValue: stringValue)!
}
let persons = try decoder.decode([Person].self, from: jsonData)
return persons
}
}
// MARK: - Person Model -
struct Person: Decodable {
let name: String
}
// MARK: - Responses -
// The JSON response from service 1.
let service1JSONResponse = """
[ {  "name": "Peter", } ]
""".data(using: .utf8)!
// The JSON response from service 2.
let service2JSONResponse = """
[ { "person_name": "Paul", } ]
""".data(using: .utf8)!
// MARK: - Sample Calls -
print((try? Services.service1.getPersons(jsonData: service1JSONResponse))!)
print((try? Services.service2.getPersons(jsonData: service2JSONResponse))!)

相关内容

  • 没有找到相关文章

最新更新