Merging Data for POST request

so I have a struct that I need to send the data as the payload to a POST request. But I also need to append more data to that payload along with the struct. How do I merge the data together.

struct Series: Codable, Hashable, Identifiable {
    let id: Int
    let title: String
    let overview: String

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    static func == (lhs: Series, rhs: Series) -> Bool {
        lhs.id == rhs.id
    }

    enum CodingKeys: String, CodingKey {
        case id
        case title
        case overview
    }
}

let series = Series(...args)

// Encode
let jsonData = try? JSONEncoder().encode(series)

let apendedData: //Not sure what to type this as = [
    "foo": true,
    "bar": {
        "baz": true
    }
]

/* Expected payload

{
     "id: 1,
     "title": "Random title",
     "overview": "This is an overview",
     "foo": true,
     "bar": {
         "baz": true
     }
}
 */

It's not obvious what you are after. E.g. adding additional data right after JSON is one thing, adding it as another field in JSON is another, MIME - yet another, adding extra parameters as part of the query string or headers - yet another again.

Here's an ideia


struct Series: Codable {
    
    let id: Int
    let title: String
    let overview: String
}

let series = Series(id: 1, title: "title", overview: "overview")

let seriesData = try! JSONEncoder().encode(series)

let seriesJSON = try! JSONSerialization.jsonObject(with: seriesData, options: []) as! [String: Any]

let additionalData = """
{
    "foo": true,
    "bar": {
        "baz": true
    }
}
""".data(using: .utf8)!

let additionalJSON = try! JSONSerialization.jsonObject(with: additionalData, options: []) as! [String: Any]

let finalJson = seriesJSON.merging(additionalJSON, uniquingKeysWith: { (current, _) in current })

let finalString = String(data: try! JSONSerialization.data(withJSONObject: finalJson, options: [.prettyPrinted]), encoding: .utf8)!

print(finalString)

//    {
//      "bar" : {
//        "baz" : true
//      },
//      "foo" : true,
//      "id" : 1,
//      "overview" : "overview",
//      "title" : "title"
//    }

It's not necessary to use JSONSerialization here unless you're combining arbitrary values together. To stay within the Encodable world you can simply use composition with custom encoding. I'd recommend staying structured and generic if you can.

struct CommonValues<AdditionalValues: Encodable>: Encodable {
    let bar: Baz
    let additionalValues: AdditionalValues

    enum CodingKeys: CodingKey { case bar }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(bar, forKey: .bar)
        try additionalValues.encode(to: encoder)
    }
}

That will encode bar and additionalValues at the same level in the resulting object.

You could also just define container values with arbitrary arity (at least until the compiler gives up):

struct EncodeMany<First, Second, Third>: Encodable where First: Encodable, Second: Encodable, Third: Encodable {
  let first: First
  let second: Second
  let third: Third

  func encode(to encoder: Encoder) throws {
    try first.encode(to: encoder)
    try second.encode(to: encoder)
    try third.encode(to: encoder)
  }
}
1 Like