2

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?)

    }
}
Pochi
  • 13,391
  • 3
  • 64
  • 104
  • [Just encode and decode this manually](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types) – Rajmund Zawiślak Nov 06 '18 at 08:30
  • are those params always have the same value ? – Mohmmad S Nov 06 '18 at 08:36
  • @RajmundZawiślak Wouldn't I have to encode every single request manually? Since these parameters are shared among request types, but each type has its own properties. If that's the case I'd just be easier to explicitly write them as in the first case. If you mean, make the "SharedParameters" encoding behavior do it, that's what I'm asking since I couldn't find how to add it to the container above (instead of bellow). – Pochi Nov 06 '18 at 08:47
  • @Tobi No, the parameter keys are the same but the values might change based on the request. (In some cases they might even not be needed at all which is why I'm setting them as an optional) – Pochi Nov 06 '18 at 08:49
  • .... why not using protocol ? – Mohmmad S Nov 06 '18 at 08:54
  • @Tobi sorry I don't understand what you mean. – Pochi Nov 06 '18 at 08:58
  • i mean why dont you make a protocol with shared stuff and confirm to it when u need them – Mohmmad S Nov 06 '18 at 08:59
  • @Tobi because then I'd be the same as having written the sharedParams on the request as in the first "struct SpecialStoreRequest" structure. What I'm asking for is whether it is possible to make a structure be decoded on it's parent container. (This questions is mostly about learning if this is possible or not, and if it is, how to achieve it, not so much about how to make the requests) – Pochi Nov 06 '18 at 09:06
  • @Pochi can you check the latest answer update – Mohmmad S Nov 06 '18 at 10:01

1 Answers1

0

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)

}
}
Cristik
  • 30,989
  • 25
  • 91
  • 127
Mohmmad S
  • 5,001
  • 4
  • 18
  • 50
  • Sorry for the late reply. The problem with this answer is that I still end up having to create extensions for each of my "MyCustomReq" requests, because the parameters specific to each request are defined there. I apologize for not making this clearer. I'm starting to believe it's not possible with structures since I might need to use the Class function 'public mutating func superEncoder(forKey key: Self.Key) -> Encoder'. – Pochi Nov 08 '18 at 08:55
  • you need to create extension and try to use the final class function i gave u, the only way i think its possible for this case you can't change the decoder work, but u can create a new one fully from scratch to handle ur case and in my opinion it will take more time, this is the most convenient thing imo – Mohmmad S Nov 08 '18 at 09:06
  • @Pochi i have searched, and tried alot of things to come up with this answer, so i am pretty sure right when i say this is the most convenient way. gl and please mention me if u got something as i am looking forward to learn if there was – Mohmmad S Nov 08 '18 at 09:12