-1

I am creating a generic post body for my API's call, my post body is mostly same except the data parameter which is different for different API calls data acts like filler for different requirements, few below JSON post body.

Example 1:

{
  "timeStampUtc": "2019-07-29T03:29:21.729Z",
  ...
  "geoLocationInfo": {
    "latitude": 0,
    "longitude": 0,
    ...
  },
  "data": {
    "loginIdentity": "string",
    "loginPassword": "string"
  }
}

Example 2:

{
  "timeStampUtc": "2019-07-29T03:29:21.729Z",
  ...
  "geoLocationInfo": {
    "latitude": 0,
    "longitude": 0,
    ...
  },
  "data": {
    "wazId": 0,
    "regionId": 0
  }
}

Example 3:

{
  "timeStampUtc": "2019-07-29T03:29:21.729Z",
  ...
  "geoLocationInfo": {
    "latitude": 0,
    "longitude": 0,
   ...
  },
  "data": {
    "loginIdentity": "string",
    "wazID": 0
  }
}

I am using the encodable and generics to overcome this requirement, well I am able to do the first two scenarios, but struggle with the third when the data has values of different types. Following is the sample code can be directly tried in Playground

struct PostBody<T : Codable>: Codable
{
    var deviceInfo = ""
    var geoLocationInfo = ""
    var data = Dictionary<String, T>()

    enum CodingKeys: String, CodingKey
    {
        case deviceInfo, geoLocationInfo, data
    }

    init(dataDict : Dictionary<String, T>) {
        self.data = dataDict
    }

    init(from decoder : Decoder) throws {
        let container = try decoder.container(keyedBy : CodingKeys.self)
        deviceInfo = try container.decode(String.self, forKey: .deviceInfo)
        geoLocationInfo = try container.decode(String.self, forKey: .geoLocationInfo)
        data = try container.decode(Dictionary.self, forKey: .data)
    }


    func encode(to encoder : Encoder)
    {
        var container = encoder.container(keyedBy : CodingKeys.self)
        do
        {
            try container.encode(deviceInfo, forKey : .deviceInfo)
            try container.encode(geoLocationInfo, forKey : .geoLocationInfo)
            try container.encode(data, forKey : .data)
        }
        catch
        {
            fatalError("Should never happen")
        }
    }
}


let postBody = PostBody<String>(dataDict : ["1" : "1", "2" : "2"])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(postBody)
let encodedDataDict2 = try  encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)

let postBody1 = PostBody(dataDict : ["1" : 1, "2" : 2])
let encoder1 = JSONEncoder()
encoder1.outputFormatting = .prettyPrinted
try encoder1.encode(postBody1)
let encodedDataDict3 = try  encoder1.encode(postBody1)
print(String(data : encodedDataDict3, encoding : .utf8)!)
AnderCover
  • 2,488
  • 3
  • 23
  • 42
Rein rPavi
  • 3,368
  • 4
  • 22
  • 32

3 Answers3

2

Instead of generics use an enum for the different types. Feel free to add more types

enum StringOrInt : Codable {
    case string(String), integer(Int)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            let stringValue = try container.decode(String.self)
            self = .string(stringValue)
        } catch DecodingError.typeMismatch {
            let integerValue = try container.decode(Int.self)
            self = .integer(integerValue)
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let stringValue): try container.encode(stringValue)
        case .integer(let integerValue): try container.encode(integerValue)
        }
    }
}

struct PostBody: Codable
{
    let deviceInfo, geoLocationInfo : String
    let data : Dictionary<String, StringOrInt>
}


let postBody = PostBody(deviceInfo: "Foo", geoLocationInfo: "Bar", data : ["loginIdentity" : .string("string"), "wazID" : .integer(0)])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodedDataDict2 = try encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)
vadian
  • 274,689
  • 30
  • 353
  • 361
2

Swift 5.7.1 EDIT

it just works now:

struct PostBody<T: Codable>: Codable
{
    var deviceInfo = ""
    var geoLocationInfo = ""
    var data = Dictionary<String, T>()

    enum CodingKeys: String, CodingKey
    {
        case deviceInfo, geoLocationInfo, data
    }

    init(dataDict : Dictionary<String, T>) {
        self.data = dataDict
    }

    init(from decoder : Decoder) throws {
        let container = try decoder.container(keyedBy : CodingKeys.self)
        deviceInfo = try container.decode(String.self, forKey: .deviceInfo)
        geoLocationInfo = try container.decode(String.self, forKey: .geoLocationInfo)
        data = try container.decode(Dictionary<String, T>.self, forKey: .data)
    }


    func encode(to encoder : Encoder)
    {
        var container = encoder.container(keyedBy : CodingKeys.self)
        do
        {
            try container.encode(deviceInfo, forKey : .deviceInfo)
            try container.encode(geoLocationInfo, forKey : .geoLocationInfo)
            try container.encode(data, forKey : .data)
        }
        catch
        {
            fatalError("Should never happen")
        }
    }
}

let postBody = PostBody<String>(dataDict : ["1" : "1", "2" : "2"])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(postBody)
let encodedDataDict2 = try  encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)
// {
//   "geoLocationInfo" : "",
//   "deviceInfo" : "",
//   "data" : {
//     "2" : "2",
//     "1" : "1"
//  }
// }

let postBody1 = PostBody(dataDict : ["1" : 1, "2" : 2])
let encoder1 = JSONEncoder()
encoder1.outputFormatting = .prettyPrinted
try encoder1.encode(postBody1)
let encodedDataDict3 = try  encoder1.encode(postBody1)
print(String(data : encodedDataDict3, encoding : .utf8)!)
// {
//   "geoLocationInfo" : "",
//   "deviceInfo" : "",
//   "data" : {
//     "2" : 2,
//     "1" : 1
//   }
// }

AnderCover
  • 2,488
  • 3
  • 23
  • 42
0

Whenever working with json data I recommend using QuickType as this will allow you to quickly get an idea or to simply just generate code necessary for the different languages that you need.

  • This is an example based on the data you provided.

  • There are several options to play with, such as changing between Class or Struct and only using plain types. There is also the option of generating initializers and mutators.

F22lightning
  • 620
  • 6
  • 16
  • Thanks for the reply, but I don't want to hardcode my data into model or models, the idea is that the post body pattern will always be the same except the data field above, I want to pass a data dictionary and use this same post body for my all API's – Rein rPavi Jul 29 '19 at 04:54