Swift中的可编码合并

f1tvaqid  于 2023-08-02  发布在  Swift
关注(0)|答案(4)|浏览(130)

我有以下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复制SessionPerson的所有属性,如下所示,但这不是很好,因为我的真实的结构体有更多的属性。

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
    }
}

b4lqfgs4

b4lqfgs41#

调用每个可编码对象的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)
    }
}

字符串

tv6aics1

tv6aics12#

使用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"
}


让我知道如果你仍然面临任何问题。

jexiocij

jexiocij3#

我想在这里扩展@marty-suzuki的答案,因为如果你不小心的话,可能会错过一些细微的差别。下面是我的代码版本:

struct EncodableCombiner: Encodable {
    let subelements: [Encodable]
    func encode(to encoder: Encoder) throws {
        for element in subelements {
            try element.encode(to: encoder)
        }
    }
}

字符串
简单地用一个可编码对象的数组示例化,并将结果对象本身视为可编码对象。现在,使用此方法时需要记住几个重要注意事项:
1.在JSON中只能有一种类型的根对象,它可以是单个值、数组或字典。因此,当你在各种可编码对象中实现encode(to:)时,千万不要使用encoder.singleValueContainer创建容器。
1.你希望合并的每个对象都必须在同一种容器上操作,所以如果其中一个使用unkeyedContainer(),那么它们都必须使用。类似地,如果一个使用container(keyedBy:),那么其他的也必须使用。
1.如果你使用的是键控容器,那么所有组合对象中的两个变量不能共享相同的键名!否则,你会发现它们会互相覆盖,因为它们被解析到同一个字典中。
另一种可以缓解这些问题,但不会产生相同的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,键为elementAMyEncodableBelementB
这完全取决于你想要什么样的结构。

u0njafvf

u0njafvf4#

我个人觉得我的版本最方便

struct BaseParams: Encodable {
    let platform: String = "ios"
    let deviceId: String = "deviceId"
}

struct RequestModel<PayloadType: Encodable>: Encodable {
    let session = BaseParams()
    let payload: PayloadType

    public func encode(to encoder: Encoder) throws {
        try session.encode(to: encoder)
        try payload.encode(to: encoder)
    }
}

字符串
使用情况

struct TransactionParams: Encodable {
    let transation: [String]
}

let transactionParams = TransactionParams(transation: ["1", "2"])
let requestModel = RequestModel(payload: transactionParams)

let data = try JSONEncoder().encode(requestModel)
try JSONSerialization.jsonObject(with: data)


测试结果
[“平台”:“ios”,“transation”:[“1”,“2”],“deviceId”:“deviceId”]

相关问题