ios - 在 Swift 中从多个服务中解码 JSON 的更简单方法
问题描述
前提
我有一个struct
符合 的Decodable
,因此它可以从各种响应中解码 JSON init(from:)
。对于我希望解码的每种类型的 JSON 响应,我都有一个enum
符合CodingKey
.
例子
这是一个简化的示例,可以放入 Swift Playground 中:
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 }
}
完成后映射到 Persons 中:
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]
推荐阅读
- javascript - 在ReactJS中清除会话存储和本地存储后仍然出现输入字段值
- python - 在熊猫数据框中按带有分号分隔值的列分组
- ruby-on-rails - 使用活动存储播种图像
- typo3 - 错字3 8.7.x tx_news 7.0.5 用新闻标题覆盖默认元标记标题
- ios - 如何在选择 tableView 的行单元格时显示视图控制器(作为集合视图单元格)?
- php - Wordpress 按标签过滤帖子
- mongodb - MongoDB将两个数组内的值相乘
- linux - 为什么 NOP 的数量似乎会影响 shellcode 是否成功执行?
- android - 在加载活动期间将嵌套滚动视图的顶部设置为总高度的一半时遇到问题
- java - 用 Regex 替换 Java 中的空格和分号