Decoding multiple JSON requests for one struct - json

I would like to decode two JSON urls that have different fields for the same item and then combine them into one model object using a struct or class. I am able to work with one JSON url and decode it as needed, but am struggling with adding the second url. I currently have been using structs, but I will be working with large amounts of data, so I understand a class would be better to use here. Any other suggestions would be greatly appreciated. I have searched quite a bit but have been unable to find information that is relevant to this situation in swift 4.
I have below an example that I hope can be manipulated to perform what I'm trying to do. Here we have 1 endpoint that provides state, abbreviation, and region members. The other endpoint provides state, population, and area.
class CollectionViewController: UIViewController {
let urlStates1 = "https://......endpoint1"
let urlStates2 = "https://......endpoint2"
var states = [StatesData]()
override func viewDidLoad() {
super.viewDidLoad()
downloadJSON() {
self.CollectionView.reloadData()
}
}
func downloadJSON(completed:#escaping ()->()){
let url1 = URL(string: urlStates1)
URLSession.shared.dataTask(with: url1!) { (data, response, error) in
if error == nil {
do{
self.states = try JSONDecoder().decode([StatesData].self, from: data!)
DispatchQueue.main.async{
completed()
}
} catch {
print("JSON Error")
}}
}.resume()
}
Here's my struct
struct StatesData : Decodable {
//////Members from URL1///////
var state : String?
var abrev : String?
var region : String?
//////Members specific to URL2///////
var population : Int?
var area : Double?
private enum AttributeKeys: String, CodingKey {
case state = "state"
case abrev = "abrev"
case region = "region"
}
private enum StatisticsKeys: String, CodingKey {
case state = "state"
case population = "population"
case area = "area"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: AttributeKeys.self)
if let stateName = try values.decodeIfPresent(String.self, forKey: .state) {
state = stateName
abrev = try values.decodeIfPresent(String.self, forKey: .abrev)!
region = try values.decodeIfPresent(String.self, forKey: .region)!
} else {
let values = try decoder.container(keyedBy: StatisticsKeys.self)
state = try values.decodeIfPresent(String.self, forKey: .state)!
population = try values.decodeIfPresent(Int.self, forKey: .population)!
area = try values.decodeIfPresent(Double.self, forKey: .area)!
}
}
}
}
Let's say the JSON response for urlStates1 is as follows
[
{
"state": "Connecticut",
"abrev": "CT",
"region": "Northeast"
},
{
"state": "Texas",
"abrev": "TX",
"region": "Southwest"
}
]
And the JSON response for urlStates2 is as follows
[
{
"state": "Connecticut",
"population": 3588000,
"area": 5543.00
},
{
"state": "Texas",
"population": 28300000,
"area": 268597.00
}
]

In your current implementation, there are two issues in your StatesData struct.
1) Property types are not matching the value types in the response (i.e population and area).
2) Your if statement is always true in decoder as the key .state is present to both kind of responses so it never decodes second type of response.
Please see the corrected StatesData, Now you should be able to decode the responses correctly.
struct StatesData : Decodable {
//////Members from URL1///////
var state : String?
var abrev : String?
var region : String?
//////Members specific to URL2///////
var population : String?
var area : String?
private enum AttributeKeys: String, CodingKey {
case state = "state"
case abrev = "abrev"
case region = "region"
}
private enum StatisticsKeys: String, CodingKey {
case state = "state"
case population = "population"
case area = "area"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: AttributeKeys.self)
if let abrevName = try values.decodeIfPresent(String.self, forKey: .abrev) {
state = try values.decodeIfPresent(String.self, forKey: .state)
abrev = abrevName
region = try values.decodeIfPresent(String.self, forKey: .region)
} else {
let values = try decoder.container(keyedBy: StatisticsKeys.self)
state = try values.decodeIfPresent(String.self, forKey: .state)
population = try values.decodeIfPresent(String.self, forKey: .population)
area = try values.decodeIfPresent(String.self, forKey: .area)
}
}
}

Related

Swift - How to have multiple decoder method for a single model

struct Classroom: Codable {
let teacher: String
let id: Int
let status: Bool
init(from decoder: Decoder) throws {
...
...
}
}
Now I need a way to create Classroom instance with a simple String
{ "classroom": "Test" }//received from API
let classRoom = ClassRoom(teacher: "Test", id: 0, status: true)
Now I need to add a secondary decoder method which can create this classroom instance using the "classroom": "Test" data. The "Test" value should be used as value for "teacher" and other properties should contain default values.
I know I can decode the String value and create a new initializer. Is there a way to directly decode String to this model object?
if i understand well, i assume you have a bad json format like below
[
{
"teacher":"test",
"id":5,
"status":true
},
{
"classroom":"Test"
}
]
And you want to decode both objects, you can do the following
let data = """
[
{
"teacher": "test",
"id": 5,
"status": true
},
{
"classroom": "Test"
}
]
""".data(using: .utf8)!
struct Classroom: Codable {
let teacher: String
let id: Int
let status: Bool
private enum CodingKeys: CodingKey {
case teacher, id, status
}
private enum SecCodingKeys: CodingKey {
case classroom
}
init(from decoder: Decoder) throws {
let value = try decoder.container(keyedBy: CodingKeys.self)
let secValue = try decoder.container(keyedBy: SecCodingKeys.self)
let teacher_1 = try value.decodeIfPresent(String.self, forKey: .teacher)
let teacher_2 = try secValue.decodeIfPresent(String.self, forKey: .classroom)
teacher = teacher_1 ?? teacher_2 ?? ""
id = try value.decodeIfPresent(Int.self, forKey: .id) ?? 0
status = try value.decodeIfPresent(Bool.self, forKey: .status) ?? false
}
}
do {
let rooms = try JSONDecoder().decode([Classroom].self, from: data)
print(rooms.map(\.teacher))
} catch {
print(error)
}
and the result,
["test", "Test"]
Decode the second, nested case, as another type
struct SimpleClassroom: Decodable {
let classroom: String
}
and then have a computed property for mapping to the original type with default values
extension SimpleClassroom {
var classroomValue: Classroom {
Classroom(teacher: classroom, id: 0, status: false)
}
}
If "Test" is a valid description of classroom, and you want to go ahead and create the classroom, then you have a number of options.
If you know classrooms from a given API endpoint will always be in this string format, you can use the decoder's context dictionary to tell it up front which strategy to use to decode the classroom. If sometimes a classroom is a properly formed dictionary, and sometimes it's just a string, and you want to proceed either way, then you have to handle that case in the init(from:).
Either way you're looking at a custom init method. The second case, where you handle both types, would look like this:
init(from decoder: Decoder) throws {
// Do we have a single-value container?
do {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
self.teacher = string
self.id = 0
self.status = true
return
} catch {
// OK, it was a dictionary
}
let container = try decoder.container(keyedBy: CodingKeys.self)
self.teacher = try container.decode(String.self, forKey: .teacher)
self.id = try container.decode(Int.self, forKey: .id)
self.status = try container.decode(Bool.self, forKey: .status)
}
Given this made-up, horrible JSON:
[
"Test",
{ "teacher": "Mr Chips", "id": 0, "status": true }
]
let rooms = try JSONDecoder().decode([Classroom].self, from: data)
Gives you two valid Classroom types in an array.

Parsing Dynamic JSON Model in Swift [duplicate]

This question already has answers here:
Swift JSOn Decoding Error: Expected to decode Array<Any> but found a dictionary instead
(2 answers)
Closed 1 year ago.
My app must parse JSON data. The software (IceStats) that generates the data, though, generates JSON with two slightly different structures. Sometimes, the "source" value is an array of dictionaries and sometimes it just one dictionary. I can parse the JSON when it is one way or the other, but I don't know how to handle it both ways.
Here is the JSON in the Array version:
{
"icestats": {
"admin": "dontcontactme#localhost",
"host": "server.badradio.biz",
"location": "Airport",
"server_id": "Icecast 2.4.4",
"server_start": "Mon, 26 Apr 2021 12:50:47 -0500",
"server_start_iso8601": "2021-04-26T12:50:47-0500",
"source": [
{
"audio_info": "bitrate=128",
"bitrate": 128,
"genre": "Automation",
"listener_peak": 7,
"listeners": 0,
"listenurl": "http://server.badradio.biz:8000/ambient",
"server_description": "No show is running, tune in for selections from the venerable tape series \"Comfortable & Economical\"",
"server_name": "Comfortable & Economical",
"server_type": "audio/mpeg",
"server_url": "badradio.biz",
"stream_start": "Fri, 30 Apr 2021 06:51:49 -0500",
"stream_start_iso8601": "2021-04-30T06:51:49-0500",
"title": "Vol-15-A",
"dummy": null
},
{
"listeners": 0,
"listenurl": "http://server.badradio.biz:8000/stream",
"dummy": null
}
]
}
}
And here it is in the Dictionary version:
{
"icestats": {
"admin": "dontcontactme#localhost",
"host": "server.badradio.biz",
"location": "Airport",
"server_id": "Icecast 2.4.4",
"server_start": "Mon, 26 Apr 2021 12:50:47 -0500",
"server_start_iso8601": "2021-04-26T12:50:47-0500",
"source": {
"audio_info": "bitrate=128",
"bitrate": 128,
"genre": "Automation",
"listener_peak": 2,
"listeners": 0,
"listenurl": "http://server.badradio.biz:8000/ambient",
"server_description": "No show is running, tune in for selections from the venerable tape series \"Comfortable & Economical\"",
"server_name": "Comfortable & Economical",
"server_type": "audio/mpeg",
"server_url": "badradio.biz",
"stream_start": "Wed, 28 Apr 2021 02:18:31 -0500",
"stream_start_iso8601": "2021-04-28T02:18:31-0500",
"title": "Vol-13-A",
"dummy": null
}
}
}
Finally, here is my data model that handles the Array version:
import Foundation
struct StreamData: Decodable {
let icestats: IceStats
}
struct IceStats: Decodable {
let source: [Source]
}
struct Source: Decodable {
let server_name: String?
let stream_start: String?
let title: String?
let server_description: String?
let server_url: String?
let genre: String?
}
Any help is greatly appreciated. Ideally, I could just change the format of the JSON, but I am not able to.
You can add some custom decoding logic to try both cases:
struct IceStats: Decodable {
var source: [Source]
enum CodingKeys : CodingKey {
case source
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let elements = try? container.decode([Source].self, forKey: .source) {
source = elements
} else if let element = try? container.decode(Source.self, forKey: .source) {
source = [element]
} else {
throw DecodingError.dataCorruptedError(forKey: .source, in: container, debugDescription: "Must be either a single or multiple sources!")
}
}
}
But this can get really long if you also want to decode other properties in IceStats, because you'll have to manually write the decoding code for those too. To avoid this, you can use a property wrapper:
#propertyWrapper
struct MultipleOrSingle<Element: Decodable>: Decodable {
let wrappedValue: [Element]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let elements = try? container.decode([Element].self) {
wrappedValue = elements
} else if let element = try? container.decode(Element.self) {
wrappedValue = [element]
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Container neither contains a single, nor multiple \(Element.self)!")
}
}
}
// Now you can just do this!
struct IceStats: Decodable {
#MultipleOrSingle
var source: [Source]
}
As per your array response your class looks like below and where you get response just decode that response class you will get all data
import Foundation
struct Response : Codable {
let icestats : Icestat?
enum CodingKeys: String, CodingKey {
case icestats = "icestats"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
icestats = Icestat(from: decoder)
}
}
struct Icestat : Codable {
let admin : String?
let host : String?
let location : String?
let serverId : String?
let serverStart : String?
let serverStartIso8601 : String?
let source : [Source]?
enum CodingKeys: String, CodingKey {
case admin = "admin"
case host = "host"
case location = "location"
case serverId = "server_id"
case serverStart = "server_start"
case serverStartIso8601 = "server_start_iso8601"
case source = "source"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
admin = try values.decodeIfPresent(String.self, forKey: .admin)
host = try values.decodeIfPresent(String.self, forKey: .host)
location = try values.decodeIfPresent(String.self, forKey: .location)
serverId = try values.decodeIfPresent(String.self, forKey: .serverId)
serverStart = try values.decodeIfPresent(String.self, forKey: .serverStart)
serverStartIso8601 = try values.decodeIfPresent(String.self, forKey: .serverStartIso8601)
source = try values.decodeIfPresent([Source].self, forKey: .source)
}
}
struct Source : Codable {
let audioInfo : String?
let bitrate : Int?
let dummy : AnyObject?
let genre : String?
let listenerPeak : Int?
let listeners : Int?
let listenurl : String?
let serverDescription : String?
let serverName : String?
let serverType : String?
let serverUrl : String?
let streamStart : String?
let streamStartIso8601 : String?
let title : String?
enum CodingKeys: String, CodingKey {
case audioInfo = "audio_info"
case bitrate = "bitrate"
case dummy = "dummy"
case genre = "genre"
case listenerPeak = "listener_peak"
case listeners = "listeners"
case listenurl = "listenurl"
case serverDescription = "server_description"
case serverName = "server_name"
case serverType = "server_type"
case serverUrl = "server_url"
case streamStart = "stream_start"
case streamStartIso8601 = "stream_start_iso8601"
case title = "title"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
audioInfo = try values.decodeIfPresent(String.self, forKey: .audioInfo)
bitrate = try values.decodeIfPresent(Int.self, forKey: .bitrate)
dummy = try values.decodeIfPresent(AnyObject.self, forKey: .dummy)
genre = try values.decodeIfPresent(String.self, forKey: .genre)
listenerPeak = try values.decodeIfPresent(Int.self, forKey: .listenerPeak)
listeners = try values.decodeIfPresent(Int.self, forKey: .listeners)
listenurl = try values.decodeIfPresent(String.self, forKey: .listenurl)
serverDescription = try values.decodeIfPresent(String.self, forKey: .serverDescription)
serverName = try values.decodeIfPresent(String.self, forKey: .serverName)
serverType = try values.decodeIfPresent(String.self, forKey: .serverType)
serverUrl = try values.decodeIfPresent(String.self, forKey: .serverUrl)
streamStart = try values.decodeIfPresent(String.self, forKey: .streamStart)
streamStartIso8601 = try values.decodeIfPresent(String.self, forKey: .streamStartIso8601)
title = try values.decodeIfPresent(String.self, forKey: .title)
}
}
decode it like
let jsonData = jsonString.data(using: .utf8)!
let response = try! JSONDecoder().decode(Response.self, from: jsonData)
print(response.icestats)

Decoding two different JSON responses in one model class using Codable

Based on the requirement I got two different kinds of response from api. That is
{
"shopname":"xxx",
"quantity":4,
"id":1,
"price":200.00,
}
another response
{
"storename":"xxx",
"qty":4,
"id":1,
"amount":200.00,
}
Here both json values are decoding in same model class. Kindly help me to resolve this concern.
is it is possible to set value in single variable like qty and quantity both are stored in same variable based on key param availability
Here's an approach that lets you have only one property in your code, instead of two Optionals:
Define a struct that contains all the properties you need, with the names that you'd like to use in your code. Then, define two CodingKey enums that map those properties to the two different JSON formats and implement a custom initializer:
let json1 = """
{
"shopname":"xxx",
"quantity":4,
"id":1,
"price":200.00,
}
""".data(using: .utf8)!
let json2 = """
{
"storename":"xxx",
"qty":4,
"id":1,
"amount":200.00,
}
""".data(using: .utf8)!
struct DecodingError: Error {}
struct Model: Decodable {
let storename: String
let quantity: Int
let id: Int
let price: Double
enum CodingKeys1: String, CodingKey {
case storename = "shopname"
case quantity
case id
case price
}
enum CodingKeys2: String, CodingKey {
case storename
case quantity = "qty"
case id
case price = "amount"
}
init(from decoder: Decoder) throws {
let container1 = try decoder.container(keyedBy: CodingKeys1.self)
let container2 = try decoder.container(keyedBy: CodingKeys2.self)
if let storename = try container1.decodeIfPresent(String.self, forKey: CodingKeys1.storename) {
self.storename = storename
self.quantity = try container1.decode(Int.self, forKey: CodingKeys1.quantity)
self.id = try container1.decode(Int.self, forKey: CodingKeys1.id)
self.price = try container1.decode(Double.self, forKey: CodingKeys1.price)
} else if let storename = try container2.decodeIfPresent(String.self, forKey: CodingKeys2.storename) {
self.storename = storename
self.quantity = try container2.decode(Int.self, forKey: CodingKeys2.quantity)
self.id = try container2.decode(Int.self, forKey: CodingKeys2.id)
self.price = try container2.decode(Double.self, forKey: CodingKeys2.price)
} else {
throw DecodingError()
}
}
}
do {
let j1 = try JSONDecoder().decode(Model.self, from: json1)
print(j1)
let j2 = try JSONDecoder().decode(Model.self, from: json2)
print(j2)
} catch {
print(error)
}
Handling different key names in single model
Below are two sample json(dictionaries) that have some common keys (one, two) and a few different keys (which serve the same purpose of error).
Sample json:
let error_json:[String: Any] = [
"error_code": 404, //different
"error_message": "file not found", //different
"one":1, //common
"two":2 //common
]
let failure_json:[String: Any] = [
"failure_code": 404, //different
"failure_message": "file not found", //different
"one":1, //common
"two":2 //common
]
CommonModel
struct CommonModel : Decodable {
var code: Int?
var message: String?
var one:Int //common
var two:Int? //common
private enum CodingKeys: String, CodingKey{ //common
case one, two
}
private enum Error_CodingKeys : String, CodingKey {
case code = "error_code", message = "error_message"
}
private enum Failure_CodingKeys : String, CodingKey {
case code = "failure_code", message = "failure_message"
}
init(from decoder: Decoder) throws {
let commonValues = try decoder.container(keyedBy: CodingKeys.self)
let errors = try decoder.container(keyedBy: Error_CodingKeys.self)
let failures = try decoder.container(keyedBy: Failure_CodingKeys.self)
///common
self.one = try commonValues.decodeIfPresent(Int.self, forKey: .one)!
self.two = try commonValues.decodeIfPresent(Int.self, forKey: .two)
/// different
if errors.allKeys.count > 0{
self.code = try errors.decodeIfPresent(Int.self, forKey: .code)
self.message = try errors.decodeIfPresent(String.self, forKey: .message)
}
if failures.allKeys.count > 0{
self.code = try failures.decodeIfPresent(Int.self, forKey: .code)
self.message = try failures.decodeIfPresent(String.self, forKey: .message)
}
}
}
Below extension will help you to convert your dictionary to data.
public extension Decodable {
init(from: Any) throws {
let data = try JSONSerialization.data(withJSONObject: from, options: .prettyPrinted)
let decoder = JSONDecoder()
self = try decoder.decode(Self.self, from: data)
}
}
Testing
public func Test_codeble(){
do {
let err_obj = try CommonModel(from: error_json)
print(err_obj)
let failed_obj = try CommonModel(from: failure_json)
print(failed_obj)
}catch let error {
print(error.localizedDescription)
}
}
Use like
struct modelClass : Codable {
let amount : Float?
let id : Int?
let price : Float?
let qty : Int?
let quantity : Int?
let shopname : String?
let storename : String?
enum CodingKeys: String, CodingKey {
case amount = "amount"
case id = "id"
case price = "price"
case qty = "qty"
case quantity = "quantity"
case shopname = "shopname"
case storename = "storename"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
amount = try values.decodeIfPresent(Float.self, forKey: .amount)
id = try values.decodeIfPresent(Int.self, forKey: .id)
price = try values.decodeIfPresent(Float.self, forKey: .price)
qty = try values.decodeIfPresent(Int.self, forKey: .qty)
quantity = try values.decodeIfPresent(Int.self, forKey: .quantity)
shopname = try values.decodeIfPresent(String.self, forKey: .shopname)
storename = try values.decodeIfPresent(String.self, forKey: .storename)
}
}

How to handle Empty array in codable decode process

It works well when "address" array is not empty. But it fails when "address" array is empty. Any help will be highly appreciated. I have struct for Address object. Basically "address" is array of object of type "Address" but when address is empty it fails.
{
"success": "1",
"message": "You have succesfully verified your mobile no",
"details": {
"customer_id": 825,
"is_delivery_available": "0",
"is_registration_complete": "0",
"is_customer_verified": "0",
"customer_status": "0",
"cart_count": "0",
"name_type": "mr",
"firstname": "",
"lastname": "",
"full_name": "",
"pincode": "",
"profile_pic": "",
"mobile": "8583846677",
"email": "",
"address": [
],
"referral_code": ""
}
}
Above is the JSON I am trying to decode using Codable in Swift4.
import Foundation
struct Signup: Codable {
var success:String?
var message:String?
var details:Details?
private enum CodingKeys: String, CodingKey { case success, message, details }
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
success = try values.decode(String.self, forKey: .success)
message = try values.decode(String.self, forKey: .message)
if let _details = try? values.decode(Details.self, forKey: .details) {
details = _details
}
}
}
struct Details: Codable {
var address:[Address]?
var cart_count:String?
var customer_id:String?
var customer_status:String?
var email:String?
var firstname:String?
var full_name:String?
var is_customer_verified:String?
var is_delivery_available:String?
var is_registration_complete:String?
var lastname:String?
var mobile:String?
var name_type:String?
var pincode:String?
var profile_pic:String?
var referral_code:String?
private enum CodingKeys: String, CodingKey { case address, cart_count, customer_id, customer_status, email, firstname, full_name, is_customer_verified, is_delivery_available, is_registration_complete, lastname, mobile, name_type, pincode, profile_pic, referral_code }
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
****-->>> Issue is in this line. How to handle empty array while decoding. <<<--****
address = try? values.decode([Address].self, forKey: .address)
cart_count = try values.decode(String.self, forKey: .cart_count)
customer_id = try values.decode(String.self, forKey: .customer_id)
customer_status = try values.decode(String.self, forKey: .customer_status)
email = try values.decode(String.self, forKey: .email)
firstname = try values.decode(String.self, forKey: .firstname)
full_name = try values.decode(String.self, forKey: .full_name)
is_customer_verified = try values.decode(String.self, forKey: .is_customer_verified)
is_delivery_available = try values.decode(String.self, forKey: .is_delivery_available)
is_registration_complete = try values.decode(String.self, forKey: .is_registration_complete)
lastname = try values.decode(String.self, forKey: .lastname)
mobile = try values.decode(String.self, forKey: .mobile)
name_type = try values.decode(String.self, forKey: .name_type)
pincode = try values.decode(String.self, forKey: .pincode)
profile_pic = try values.decode(String.self, forKey: .profile_pic)
referral_code = try values.decode(String.self, forKey: .referral_code)
}
}
struct Address: Codable {
var address_default:String?
var address_id:String?
var address_type:String?
var city:String?
var customer_id:String?
var flat_or_house_or_office_no:String?
var full_address:String?
var full_name:String?
var landmark:String?
var lat:String?
var lng:String?
var mobile:String?
var name_type:String?
var pincode:String?
var street_or_society_or_office_name:String?
}
I am stuck into this. How to handle empty array while decoding the json data in Swift4 Codable.
I came across this problem and I did as below to solve this one.
if let _address = try? values.decode([Address].self, forKey: .address) {
address = _address
} else {
address = []
}
You can give it a try.
2022.06.08 updated.
according to #Amin Benarieb
let _address = try? values.decode([Address].self, forKey: .address) ?? []
The real issue is that customer_id in Details is Int not String. Decoding an empty array is supposed to work.
Your code is too complicated. Enjoy the magic of Codable and omit all CodingKeys and initializers and declare all struct members as non-optional and as constants
struct Signup: Decodable {
let success : String
let message : String
let details : Details
}
struct Details : Decodable {
let address : [Address]
let cart_count : String
let customer_id : Int
let customer_status, email, firstname, full_name : String
let is_customer_verified, is_delivery_available, is_registration_complete : String
let lastname, mobile, name_type, pincode, profile_pic, referral_code : String
}
struct Address: Decodable {
let address_default, address_id, address_type, city, customer_id : String
let flat_or_house_or_office_no, full_address, full_name : String
let landmark, lat, lng, mobile, name_type, pincode, street_or_society_or_office_name : String
}
You can even name the members camelCased
let cartCount : String
let customerId : Int
...
let isRegistrationComplete : String
and pass the convertFromSnakeCase key decoding strategy
decoder.keyDecodingStrategy = .convertFromSnakeCase
Still no extra code!

swift4 encoding decoding for model class of nested json parsing

I have a model class of swift which was created based on a nested json response, it follows like below
struct RootClass : Codable {
let details : String?
let itemCount : Int?
let list : [List]?
enum CodingKeys: String, CodingKey {
case details = "Details"
case itemCount = "ItemCount"
case list = "List"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
details = try values.decodeIfPresent(String.self, forKey: .details)
itemCount = try values.decodeIfPresent(Int.self, forKey: .itemCount)
list = try values.decodeIfPresent([List].self, forKey: .list)
}
}
struct List : Codable {
let companyID : Int?
let employeeCount : Int?
let employeeUser : EmployeeUser?
enum CodingKeys: String, CodingKey {
case companyID = "CompanyID"
case employeeCount = "EmployeeCount"
case employeeUser = "EmployeeUser"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
companyID = try values.decodeIfPresent(Int.self, forKey: .companyID)
employeeCount = try values.decodeIfPresent(Int.self, forKey: .employeeCount)
employeeUser = try EmployeeUser(from: decoder)
}
}
struct EmployeeUser : Codable {
let mobileNumber : String?
let name : String?
enum CodingKeys: String, CodingKey {
case mobileNumber = "MobileNumber"
case name = "Name"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
mobileNumber = try values.decodeIfPresent(String.self, forKey: .mobileNumber)
name = try values.decodeIfPresent(String.self, forKey: .name)
}
}
and my json response is
{
"Details": null,
"List": [
{
"CompanyID": 140,
"EmployeeUser": {
"Name": " raghu2",
"MobileNumber": "8718718710"
},
"EmployeeCount": 0
},
{
"CompanyID": 140,
"EmployeeUser": {
"Name": "new emp reg",
"MobileNumber": "1"
},
"EmployeeCount": 0
}
],
"ItemCount": 0
}
I am trying to parse it like
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(RootClass.self, from: data)
print(gitData.itemCount ?? "")
print(gitData.list![0].employeeUser?.mobileNumber ?? "")
}
catch let err {
print("Err", err)
}
I am able to get the values of root class and list but I am getting nil values under employee user section.
Your code a few problems:
All your keys are optional. The vendor API will tell you what keys are always present and which one are optional. Follow that.
decodeIfPresent will silently fail if it cannot decode a key. When debugging your app, you want things to fail with a bang so you can fix the error before going to production.
You wrote way more code than needed. All those init(from decoder: ) functions are not needed. One one did cause your problem.
Your problem was caused by this line:
struct List : Codable {
init(from decoder: Decoder) throws {
...
employeeUser = try EmployeeUser(from: decoder)
}
}
You are asking Swift to decode to same JSON to a List and a EmployeeUser object. Obviously, that's not valid. But when you decode list inside RootClass, you call decodeIfPresent:
// In Rootclass
list = try values.decodeIfPresent([List].self, forKey: .list)
This call silently failed and you never knew what the problem was!
Solution
Change how you initialize employeeUser to this:
employeeUser = try values.decodeIfPresent(EmployeeUser.self, forKey: .employeeUser)
But the most elegant solution is to delete all those init(from decoder: ). The compiler will synthesize them for you.
And finally, fix those optionals!