I'm using a protocol to encode the conforming structures:

protocol RequestParameters: Encodable {

}

extension RequestParameters {

    func dataEncoding() -> Data? {
        guard let data = try? JSONEncoder().encode(self) else { return nil }
        return data
    }
}

This works perfectly fine for encoding these kind of structures:

struct StoreRequest: RequestParameters {

    var storeCode : String

    var storeNumber : String    
}

However, sometimes my requests require some "shared" parameters:

struct SpecialStoreRequest: RequestParameters {

    var storeCode : String

    var storeNumber : String  

    // Shared Parameters that appear in 70% of my requests
    var sharedParam1 : String?
    var sharedParam2 : String?
    var sharedParam3 : String?
    var sharedParam4 : String?
    var sharedParam5 : String?
}

I could simply write these shared parameters on each of my request structures that need them, but I was wondering if it is possible to group them in another structure and somehow modify the encoding to encode them on the top level instead?

I'm thinking of something similar to this:

struct SharedParameters {

    // Shared Parameters that appear in 70% of my requests
    var sharedParam1: String?
    var sharedParam2: String?
    var sharedParam3: String?
    var sharedParam4: String?
    var sharedParam5: String?

    enum CodingKeys: String, CodingKey {
        case sharedParam1
        case sharedParam2
        case sharedParam3
        case sharedParam4
        case sharedParam5
    }
}

struct SpecialStoreRequest: RequestParameters {

    var storeCode : String

    var storeNumber : String  

    var sharedParams : SharedParameters?
}

The problem with this last structure is that the resulting encoding is NOT the same as the first one because my shared parameters will be encoded INSIDE the "sharedParams" key:

{
   "storeCode" : "ABC",
   "storeNumber" : "123456",
   "sharedParams" : {"sharedParam1" : "A","sharedParam2" : "B", ...}
}

But what I need is for them be encoded along my other existing parameters (storeCode & storeNumber in this case).

{
   "storeCode" : "ABC",
   "storeNumber" : "123456",
   "sharedParam1" : "A",
   "sharedParam2" : "B", 
   ...
}

EDIT: To make the question clearer, assuming it is possible, what should go here to make this structure be encoded by key-value directly on its parent?

extension SharedParameters: Encodable {

    func encode(to encoder: Encoder) throws {

        // What goes here? (Is it even possible?)

    }
}
1

There are 1 best solutions below

3
On

was wondering if it is possible to group them in another structure and somehow modify the encoding to encode them on the top level instead?

You can't change the current Encoder and how it behaves but,

You can achieve that by customizing the Encode functions,

make two containers and use the shared parameters CodingKeys to encode

parameters inside your sharedParameters variable.

Observe the code Below.

    struct Others: Codable {
    var sharedParam1: String
    var sharedParam2: String

    enum CodingKeys: String, CodingKey {
        case sharedParam1
        case sharedParam2
    }
}



  struct MyCustomReq: Codable {
    var p1: String

    var p2: String

    var shared: Others

    enum CodingKeys: String, CodingKey {
        case p1
        case p2
        case shared
    }
}

    extension MyCustomReq {
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(p1, forKey: .p1)
            try container.encode(p2, forKey: .p2)
            //Others = sharedParams container. with its CodingKeys
            var container2 = encoder.container(keyedBy: Others.CodingKeys.self)
            try container2.encode(shared.sharedParam1, forKey: .sharedParam1 )
            try container2.encode(shared.sharedParam1, forKey: .sharedParam2)
        }
    }

Usage Test

var oth = Others(sharedParam1: "Shared1", sharedParam2: "Shared2")
var object = MyCustomReq.init(p1: "P1", p2: "P2", shared: oth)

let encoder = JSONEncoder()
let data = try encoder.encode(object)

print(String(data: data, encoding: .utf8)!)

OUTPUT

{ "p2":"P2",

"sharedParam1":"Shared1",

"p1":"P1",

"sharedParam2":"Shared1"

}

Now lets take it to the next step,

Create a class and custom the Encoder of the shared there and just call its function.

Observe the final result.

final class MyParamsEncoded: Codable {
var sharedParams: Others
init (sharedParam: Others) {
    self.sharedParams = sharedParam
}
func encode(to encoder: Encoder) throws {
    var container2 = encoder.container(keyedBy: Others.CodingKeys.self)
    try container2.encode(sharedParams.sharedParam1, forKey: .sharedParam1 )
    try container2.encode(sharedParams.sharedParam1, forKey: .sharedParam2)
}
}

Now after Adding this class you can just use it like this, And it will give you the same result.

extension MyCustomReq {
func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(p1, forKey: .p1)
    try container.encode(p2, forKey: .p2)
    //Using the class wrapping, final Result of using 
    var cont = try MyParamsEncoded(sharedParam: shared).encode(to: encoder)

}
}