在 Swift 中合并 Encodable
Merge Encodable in Swift
我有以下 Swift 结构
struct Session: Encodable {
let sessionId: String
}
struct Person: Encodable {
let name: String
let age: Int
}
let person = Person(name: "Jan", age: 36)
let session = Session(sessionId: "xyz")
我需要编码为具有以下格式的 json 对象:
{
"name": "Jan",
"age": 36,
"sessionId": "xyz"
}
其中 Session
的所有键都合并到 Person
的键中
我考虑过使用带有自定义 Encodable
实现的容器结构,其中我使用 SingleValueEncodingContainer
但它显然只能编码一个值
struct RequestModel: Encodable {
let session: Session
let person: Person
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(person)
// crash
try container.encode(session)
}
}
let person = Person(name: "Jan", age: 36)
let session = Session(sessionId: "xyz")
let requestModel = RequestModel(session: session, person: person)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(requestModel)
let json = String(data: data, encoding: .utf8)!
print(json)
我无法更改 json 格式,因为它是固定网络 API。我可以将 sessionId
作为 Person
的 属性,但我想避免这种情况,因为它们是不相关的模型。
另一种方法是让 RequestModel
从 Session
和 Person
复制所有属性,如下所示,但这不是很好,因为我的真实结构有更多的属性。
struct RequestModel: Encodable {
let sessionId: String
let name: String
let age: Int
init(session: Session, person: Person) {
sessionId = session.sessionId
name = person.name
age = person.age
}
}
使用encoder.container(keyedBy: CodingKeys.self)
代替singleValueContainer()
并单独添加键值对,即
struct RequestModel: Encodable
{
let session: Session
let person: Person
enum CodingKeys: String, CodingKey {
case sessionId, name, age
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(person.age, forKey: RequestModel.CodingKeys.age)
try container.encode(person.name, forKey: RequestModel.CodingKeys.name)
try container.encode(session.sessionId, forKey: RequestModel.CodingKeys.sessionId)
}
}
输出:
{
"age" : 36,
"name" : "Jan",
"sessionId" : "xyz"
}
如果您仍然遇到任何问题,请告诉我。
调用每个可编码对象的 encode(to:)
,而不是 singleValueContainer()
。
无需定义额外的 CodingKeys
.
即可将多个可编码对象合并为一个可编码对象
struct RequestModel: Encodable {
let session: Session
let person: Person
public func encode(to encoder: Encoder) throws {
try session.encode(to: encoder)
try person.encode(to: encoder)
}
}
我想在这里扩展@marty-suzuki 的回答,因为如果您不小心,可能会错过一些细微差别。这是我的代码版本:
struct EncodableCombiner: Encodable {
let subelements: [Encodable]
func encode(to encoder: Encoder) throws {
for element in subelements {
try element.encode(to: encoder)
}
}
}
只需使用可编码对象数组进行实例化,并将生成的对象本身视为可编码对象。现在,使用此方法时需要记住几个重要注意事项:
- 您的 JSON 中只能有一种类型的根对象,它可以是单个值、数组或字典。因此,当您在各种可编码对象中实现
encode(to:)
时,切勿使用 encoder.singleValueContainer
. 创建容器
- 您希望组合的每个对象都必须在同一类型的容器上运行,因此如果其中一个使用
unkeyedContainer()
,它们也必须全部使用。同样,如果一个人使用 container(keyedBy:)
,那么其他人也必须使用。
- 如果您使用键控容器,那么所有组合对象中的两个变量都不能共享相同的键名!否则,您会发现它们相互覆盖,因为它们被解析到同一个字典中。
可以缓解这些问题但不会产生相同 JSON 结构的替代方法是:
struct EncodableCombiner: Encodable {
let elementA: MyEncodableA
let elementB: MyEncodableB
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(elementA)
try container.encode(elementB)
}
}
现在,这有点不太方便,因为我们不能简单地提供一个符合 Encodable 的对象数组;它需要确切地知道他们要称呼什么 container.encode()
。结果是一个 JSON 对象,其中一个数组作为其根对象,每个子元素表示为该数组中的一个元素。事实上,你可以进一步简化它:
struct EncodableCombiner: Encodable {
let elementA: MyEncodableA
let elementB: MyEncodableB
}
... 这当然会产生一个字典根对象,其编码形式为 MyEncodableA
键为 elementA
,MyEncodableB
为 elementB
。
这完全取决于你想要什么样的结构。
我有以下 Swift 结构
struct Session: Encodable {
let sessionId: String
}
struct Person: Encodable {
let name: String
let age: Int
}
let person = Person(name: "Jan", age: 36)
let session = Session(sessionId: "xyz")
我需要编码为具有以下格式的 json 对象:
{
"name": "Jan",
"age": 36,
"sessionId": "xyz"
}
其中 Session
的所有键都合并到 Person
我考虑过使用带有自定义 Encodable
实现的容器结构,其中我使用 SingleValueEncodingContainer
但它显然只能编码一个值
struct RequestModel: Encodable {
let session: Session
let person: Person
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(person)
// crash
try container.encode(session)
}
}
let person = Person(name: "Jan", age: 36)
let session = Session(sessionId: "xyz")
let requestModel = RequestModel(session: session, person: person)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(requestModel)
let json = String(data: data, encoding: .utf8)!
print(json)
我无法更改 json 格式,因为它是固定网络 API。我可以将 sessionId
作为 Person
的 属性,但我想避免这种情况,因为它们是不相关的模型。
另一种方法是让 RequestModel
从 Session
和 Person
复制所有属性,如下所示,但这不是很好,因为我的真实结构有更多的属性。
struct RequestModel: Encodable {
let sessionId: String
let name: String
let age: Int
init(session: Session, person: Person) {
sessionId = session.sessionId
name = person.name
age = person.age
}
}
使用encoder.container(keyedBy: CodingKeys.self)
代替singleValueContainer()
并单独添加键值对,即
struct RequestModel: Encodable
{
let session: Session
let person: Person
enum CodingKeys: String, CodingKey {
case sessionId, name, age
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(person.age, forKey: RequestModel.CodingKeys.age)
try container.encode(person.name, forKey: RequestModel.CodingKeys.name)
try container.encode(session.sessionId, forKey: RequestModel.CodingKeys.sessionId)
}
}
输出:
{
"age" : 36,
"name" : "Jan",
"sessionId" : "xyz"
}
如果您仍然遇到任何问题,请告诉我。
调用每个可编码对象的 encode(to:)
,而不是 singleValueContainer()
。
无需定义额外的 CodingKeys
.
struct RequestModel: Encodable {
let session: Session
let person: Person
public func encode(to encoder: Encoder) throws {
try session.encode(to: encoder)
try person.encode(to: encoder)
}
}
我想在这里扩展@marty-suzuki 的回答,因为如果您不小心,可能会错过一些细微差别。这是我的代码版本:
struct EncodableCombiner: Encodable {
let subelements: [Encodable]
func encode(to encoder: Encoder) throws {
for element in subelements {
try element.encode(to: encoder)
}
}
}
只需使用可编码对象数组进行实例化,并将生成的对象本身视为可编码对象。现在,使用此方法时需要记住几个重要注意事项:
- 您的 JSON 中只能有一种类型的根对象,它可以是单个值、数组或字典。因此,当您在各种可编码对象中实现
encode(to:)
时,切勿使用encoder.singleValueContainer
. 创建容器
- 您希望组合的每个对象都必须在同一类型的容器上运行,因此如果其中一个使用
unkeyedContainer()
,它们也必须全部使用。同样,如果一个人使用container(keyedBy:)
,那么其他人也必须使用。 - 如果您使用键控容器,那么所有组合对象中的两个变量都不能共享相同的键名!否则,您会发现它们相互覆盖,因为它们被解析到同一个字典中。
可以缓解这些问题但不会产生相同 JSON 结构的替代方法是:
struct EncodableCombiner: Encodable {
let elementA: MyEncodableA
let elementB: MyEncodableB
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(elementA)
try container.encode(elementB)
}
}
现在,这有点不太方便,因为我们不能简单地提供一个符合 Encodable 的对象数组;它需要确切地知道他们要称呼什么 container.encode()
。结果是一个 JSON 对象,其中一个数组作为其根对象,每个子元素表示为该数组中的一个元素。事实上,你可以进一步简化它:
struct EncodableCombiner: Encodable {
let elementA: MyEncodableA
let elementB: MyEncodableB
}
... 这当然会产生一个字典根对象,其编码形式为 MyEncodableA
键为 elementA
,MyEncodableB
为 elementB
。
这完全取决于你想要什么样的结构。