Related
Decoding fails if keys not present. How to safely decode if missing keys also.
I have gone through that use Optional or Nil values but still not able to decoding the objects.
Below my Json
{
"mandatory":true,
"dynamic_obj":[
{
"dt":"2021-09-22 01:29:52",
"url":"https://res._22_01_29.pdf",
"desc":"PAN CARD",
"flag":1,
"count":"2",
"field":"pan_card",
"address":"300-435, Nattu Muthu St, Sheethammal Colony, Venus Colony, Chennai, Tamil Nadu 600018, India",
"visible":true,
"latitude":13.0389309,
"longitude":80.2473746
},
{
"url":"https://res.cloudin/no-image.jpg",
"desc":"driving License",
"count":"2",
"field":"driving_license",
"visible":true
}
]
}
Model class below
struct Dynamic_obj : Codable {
var dt : String?
var url : String?
let desc : String?
var flag : Int?
let count : String?
let field : String?
let visible : Bool?
var bankname : String = "NA"
var pdfPassword : String = "NA"
var latitude : String = "NA"
var longitude : String = "NA"
var address : String = "NA"
enum CodingKeys: String, CodingKey {
case dt = "dt"
case url = "url"
case desc = "desc"
case flag = "flag"
case count = "count"
case field = "field"
case visible = "visible"
case bankname = "bankname"
case pdfPassword = "pdfPassword"
case latitude = "latitude"
case longitude = "longitude"
case address = "address"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dt = try values.decodeIfPresent(String.self, forKey: .dt)
url = try values.decodeIfPresent(String.self, forKey: .url)
desc = try values.decodeIfPresent(String.self, forKey: .desc)
flag = try values.decodeIfPresent(Int.self, forKey: .flag)
count = try values.decodeIfPresent(String.self, forKey: .count)
field = try values.decodeIfPresent(String.self, forKey: .field)
visible = try values.decodeIfPresent(Bool.self, forKey: .visible)
bankname = try values.decodeIfPresent(String.self, forKey: .bankname) ?? "NA"
pdfPassword = try values.decodeIfPresent(String.self, forKey: .pdfPassword) ?? "NA"
latitude = try values.decodeIfPresent(String.self, forKey: .latitude) ?? "NA"
longitude = try values.decodeIfPresent(String.self, forKey: .longitude) ?? "NA"
address = try values.decodeIfPresent(String.self, forKey: .address) ?? "NA"
}
}
let decoder = JSONDecoder()
do {
let responseModel = try decoder.decode(LoanDocxPending.self, from: data)
if let mandotory = responseModel.mandatory{
self.visibleDocuments["Identity Proof-~id_proof"] = idProof
self.visibleTitle.append("Identity Proof")
}
} catch {
print("error")
}
struct LoanDocxPending :Codable {
let mandatory : Bool?
var dynamic_obj : [Dynamic_obj]?
enum CodingKeys: String, CodingKey {
case mandatory = "mandatory"
case dynamic_obj = "dynamic_obj"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
mandatory = try values.decodeIfPresent(Bool.self, forKey: .mandatory)
dynamic_obj = try values.decodeIfPresent([Dynamic_obj].self, forKey: .dynamic_obj)
}
}
First of all most of your code is not needed, for example the init methods and almost all CodingKeys.
Second of all as mentioned in the comments rather than printing meaningless literal "error" print the error instance. It will tell you that latitude and longitude are Double, not String.
This model matches the JSON in the question
struct DynamicObj : Codable {
let dt : String?
let url, desc : String
let flag : Int?
let count, field : String
let visible : Bool
var bankname, pdfPassword, address : String?
var latitude, longitude : Double?
}
struct LoanDocxPending :Codable {
let mandatory : Bool
var dynamicObj : [DynamicObj]
enum CodingKeys: String, CodingKey {
case mandatory,dynamicObj = "dynamic_obj"
}
}
let decoder = JSONDecoder()
do {
let responseModel = try decoder.decode(LoanDocxPending.self, from: data)
print(responseModel.mandatory)
} catch {
print("error", error)
}
This is a part of the JSON i am getting from Github in response to a request
{
"total_count": 1657,
"incomplete_results": false,
"items": [
{
"id": 68911683,
"node_id": "MDEwOlJlcG9zaXRvcnk2ODkxMTY4Mw==",
"name": "tetros",
"full_name": "daniel-e/tetros",
"private": false,
"html_url": "https://github.com/daniel-e/tetros",
"description": "Tetris that fits into the boot sector.",
"size": 171,
"stargazers_count": 677,
"watchers_count": 677,
"language": "Assembly",
}
]
}
This is my Model
struct RepoGroup:Codable {
var items:[Repo]
}
struct Repo: Codable {
var fullName:String
var stars:Int
var watchers:Int
init(url:String,star:Int,watcher:Int) {
fullName = url
stars = star
watchers = watcher
}
enum MyStructKeys: String, CodingKey {
case fullName = "full_name"
case stars = "stargazers_count"
case watchers = "watchers_count"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MyStructKeys.self)
let fullName: String = try container.decode(String.self, forKey: .fullName)
let stars: Int = try container.decode(Int.self, forKey: .stars)
let watchers: Int = try container.decode(Int.self, forKey: .watchers)
self.init(url: fullName, star: stars, watcher: watchers)
}
}
So far so good. But as soon as i add description:String field in my model, the JSON decoder inexplicably fails to parse.
Here is my parser
let model = try JSONDecoder().decode(RepoGroup.self, from: dataResponse)
I am struggling to understand what is so special about the description field. Any kind of help would be greatly appreciated. Thanks.
Description appears to be an optional field in the GitHub API, and when a repo doesn't define a description, it is coming back as a null. This means you need to make your description field a String? and switch to using decodeIfPresent to account for the fact that it is optional.
Nothing seems off about that particular JSON to not work for description. Haven't tested this, but this is what your code looks like?
struct RepoGroup:Codable {
var items:[Repo]
}
struct Repo: Codable {
var fullName:String
var stars:Int
var watchers:Int
var description:String
init(url:String,star:Int,watcher:Int,description:String) {
fullName = url
stars = star
watchers = watcher
description = description
}
enum MyStructKeys: String, CodingKey {
case fullName = "full_name"
case stars = "stargazers_count"
case watchers = "watchers_count"
case description = "description"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MyStructKeys.self)
let fullName: String = try container.decode(String.self, forKey: .fullName)
let stars: Int = try container.decode(Int.self, forKey: .stars)
let watchers: Int = try container.decode(Int.self, forKey: .watchers)
let description: String = try container.decode(String.self, forKey: .description)
self.init(url: fullName, star: stars, watcher: watchers, description: description)
}
}
I am using Swift's Codable Protocol. I have shared the code.
I want the variable boss in the class Employee to acquire the type based on the personType String in the Person class. I want to use personType as discriminator. Response coming from the server will be different every time based on the personType value.
In Employee class, I have declared the boss variable with Person type. I want it to decode for type Employee if the personType string in the Person class is "Employee" and decode for type Boss if the personType string is "Boss". If it is null I simply want it to decode for type Person.
Any help would be really appreciated.
public class Person: Codable {
public let address: String
public let age: Int
public let name: String
public let uid: String
public let personType: String?
private enum CodingKeys: String, CodingKey {
case address
case age
case name
case uid
case personType
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
address = try container.decode(String.self, forKey: .address)
age = try container.decode(Int.self, forKey: .age)
name = try container.decode(String.self, forKey: .name)
uid = try container.decode(String.self, forKey: .uid)
personType = try container.decodeIfPresent(String.self, forKey: .personType)
}
}
public class Employee: Person {
public let department: String
public let dependents: [Person]?
public let salary: Int
public let workingDays: [Days]
public var boss: Person?
private enum CodingKeys: String, CodingKey {
case department
case dependents
case salary
case workingDays
case boss
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
department = try container.decode(String.self, forKey: .department)
dependents = try container.decode([Person].self, forKey: .dependents)
salary = try container.decode(Int.self, forKey: .salary)
workingDays = try container.decode([Days].self, forKey: .workingDays)
boss = try container.decode(Person.self, forKey: .boss)
try super.init(from: decoder)
}
}
public class Boss: Employee {
let promotedAt: Double
let assistant: Employee?
enum CodingKeys: String, CodingKey {
case promotedAt
case assistant
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
promotedAt = try container.decode(Double.self, forKey: .promotedAt)
assistant = try container.decodeIfPresent(Employee.self, forKey: .assistant)
try super.init(from: decoder)
}
}
For example in the following response, in the boss section, personType is set to 'Boss'. So it should be decoded to the Boss type. If it were 'Employee' it should automatically decode to Employee or if it null is should decode to 'Person'.
{ name: 'Shahid Khaliq',
age: 5147483645,
address: 'H # 531, S # 20',
uid: '123321',
salary: 20000,
department: 'Software Development',
workingDays: [ 'Monday', 'Tuesday', 'Friday' ],
boss:
{ personType: 'Boss',
assistant: null,
name: 'Zeeshan Ejaz',
age: 5147483645,
address: 'H # 531, S # 20',
uid: '123321',
birthday: '1994-02-13',
birthtime: '1994-02-13T14:01:54.000Z',
salary: 20000,
department: 'Software Development',
joiningDay: 'Saturday',
workingDays: [ 'Monday', 'Tuesday', 'Friday' ],
dependents: null,
hiredAt: 'Sun, 06 Nov 1994 08:49:37 GMT',
boss: null,
promotedAt: 1484719381 },
dependents: null,
hiredAt: 'Sun, 06 Nov 1994 08:49:37 GMT',
personType: null }
Need to change model as per current response you posted
public class Employee: Person {
public let department: String
public let dependents: [Person]?
public let salary: Int
public let workingDays:[String] //[Days]
public var boss: Person?
private enum CodingKeys: String, CodingKey {
case department
case dependents
case salary
case workingDays
case boss
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
department = try container.decode(String.self, forKey: .department)
//dependents = try container.decode([Person].self, forKey: .dependents)
dependents = try container.decodeIfPresent([Person].self, forKey: .dependents)
salary = try container.decode(Int.self, forKey: .salary)
// workingDays = try container.decode([Days].self, forKey: .workingDays)
workingDays = try container.decode([String].self, forKey: .workingDays)
// boss = try container.decode(Person.self, forKey: .boss)
boss = try container.decodeIfPresent(Person.self, forKey: .boss)
try super.init(from: decoder)
}
}
here added decodeIfPresent in few properties as it null as per current response
while decoding you can use different thing, i have used SwiftJSON to make code more readable ,
// i have saved response in Response.JSON file so can change response as per need while testing below code.
let file = Bundle.main.path(forResource: "Response", ofType: "JSON")
let dataURL = URL(fileURLWithPath: file!)
let data = try! Data(contentsOf: dataURL)
let jsonData = try! JSON(data: data)
//here JSON is struct which is part of SwiftyJSON
print("jsondata \(jsonData)")
do {
let bossDict = jsonData["boss"]
let dataBoss : Data = try! bossDict.rawData()
let bossType = bossDict["personType"].string
if let type = bossType {
if type == "Boss"{
let bossObj = try! JSONDecoder().decode(Boss.self, from: dataBoss)
print("boss name \(String(describing: bossObj.name))")
bossObj.listPropertiesWithValues()
}else{
// type == "Employee"
let emplyeeObj = try! JSONDecoder().decode(Employee.self, from: dataBoss)
print("Employee name \(String(describing: emplyeeObj.name))")
emplyeeObj.listPropertiesWithValues()
}
}else{
//type = nil
}
}catch{
print("exception \(error)")
}
You can download working demo of the same at link
DemoCodable
I would add a switch statement at the end of the init(from decoder:) in the Employee class
switch personType {
case "Boss":
boss = try container.decode(Boss.self, forKey: .boss)
case "Employee":
boss = try container.decode(Employee.self, forKey: .boss)
default:
boss = nil
}
From the server I have a big JSON returned that looks something like this:
{
"id": "123",
"status": "ok",
"person": {
"administration": {
"name": "John"
}
},
"company": {
"name": "Test"
}
}
I have a struct:
struct Info: Decodable, Object {
let id: String
let status: String
let personName: String
let companyName: String
}
It conforms to Decodable protocol and also is a Object (Realm entity).
My question is: Am I able somehow to decode the name of the person in personName? Something like person.administration.name.
I want the end Realm Object, to be a flat one and mostly all of the fields are strings.
Should I create separate structs for Person/Company without being Realm Objects and in decode method to set the corresponding value to "personName"?
let personName: String = try container.decode((Person.Administration.name).self, forKey: .personName)
You can simply use containers to decode nested data with Decodable, i.e.
struct Info: Decodable {
let id: String
let status: String
let personName: String
let companyName: String
enum CodingKeys: String, CodingKey {
case id, status
case person, administration
case company
case name
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
status = try values.decode(String.self, forKey: .status)
//Decoding personName
let person = try values.nestedContainer(keyedBy: CodingKeys.self, forKey: .person)
let administration = try person.nestedContainer(keyedBy: CodingKeys.self, forKey: .administration)
personName = try administration.decode(String.self, forKey: .name)
//Decoding companyName
let company = try values.nestedContainer(keyedBy: CodingKeys.self, forKey: .company)
companyName = try company.decode(String.self, forKey: .name)
}
}
Example:
I've decoded the JSON you provided above, i.e.
if let data = json.data(using: .utf8) {
let info = try? JSONDecoder().decode(Info.self, from: data)
print(info)
}
The output it gives is:
(id: "123", status: "ok", personName: "John", companyName: "Test")
You can separate out the CodingKeys for all the different levels as per your wish. I kept them at the same level for simplicity.
Suggestion: Try using the optional types with Codable. This is because the API response can be unexpected. And if you don't get any expected key-value pair, you might end up getting a nil while creating the object.
It is best practice to separate transport types you're parsing your JSON into and types to represent object in the storage.
But if you want to use this combined types you should do something like this:
struct Info: Decodable {
let id: String
let status: String
let personName: String
let companyName: String
// JSON root keys
private enum RootKeys: String, CodingKey {
case id, status, person, company
}
// Keys for "person" nested "object"
private enum PersonKeys: String, CodingKey {
case administration
}
// Keys for "administration" and "company"
private enum NamedKeys: String, CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.status = try container.decode(String.self, forKey: .status)
let personContainer = try container.nestedContainer(keyedBy: PersonKeys.self, forKey: .person)
let administrationContainer = try personContainer.nestedContainer(keyedBy: NamedKeys.self, forKey: .administration)
self.personName = try administrationContainer.decode(String.self, forKey: .name)
let companyContainer = try container.nestedContainer(keyedBy: NamedKeys.self, forKey: .company)
self.companyName = try companyContainer.decode(String.self, forKey: .name)
}
}
I separated keys into three different CodingKey types for some type safety, and to prevent accidental mixup.
While decode the json, i getting nil response. I have nested json. whether i dont know, i'm parsing right way or not.
Here is my code.
struct ProfileModelClass : Codable {
let status : Int?
let message : String?
let profile : Profile?
enum CodingKeys: String, CodingKey {
case status = "status"
case message = "message"
case profile
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
status = try values.decodeIfPresent(Int.self, forKey: .status)
message = try values.decodeIfPresent(String.self, forKey: .message)
profile = try Profile(from: decoder)
}
}
struct Profile : Codable {
let name : String?
let email : String?
let verified : Bool?
let phone : Int?
let articletype : [String]?
let ai_score : Int?
let bfi : String?
let pic : String?
let cover : String?
let background : String?
let layout : String?
let customcolors : Customcolors?
let widgets : [String]?
let basket : [Basket]?
let joindate : String?
let linklink_id : String?
let gps : Bool?
let radius : Int?
let showme : Bool?
enum CodingKeys: String, CodingKey {
case name = "name"
case email = "email"
case verified = "verified"
case phone = "phone"
case articletype = "articletype"
case ai_score = "ai_score"
case bfi = "bfi"
case pic = "pic"
case cover = "cover"
case background = "background"
case layout = "layout"
case customcolors
case widgets = "widgets"
case basket = "basket"
case joindate = "joindate"
case linklink_id = "linklink_id"
case gps = "gps"
case radius = "radius"
case showme = "showme"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
email = try values.decodeIfPresent(String.self, forKey: .email)
verified = try values.decodeIfPresent(Bool.self, forKey: .verified)
phone = try values.decodeIfPresent(Int.self, forKey: .phone)
articletype = try values.decodeIfPresent([String].self, forKey: .articletype)
ai_score = try values.decodeIfPresent(Int.self, forKey: .ai_score)
bfi = try values.decodeIfPresent(String.self, forKey: .bfi)
pic = try values.decodeIfPresent(String.self, forKey: .pic)
cover = try values.decodeIfPresent(String.self, forKey: .cover)
background = try values.decodeIfPresent(String.self, forKey: .background)
layout = try values.decodeIfPresent(String.self, forKey: .layout)
customcolors = try Customcolors(from: decoder)
widgets = try values.decodeIfPresent([String].self, forKey: .widgets)
basket = try values.decodeIfPresent([Basket].self, forKey: .basket)
joindate = try values.decodeIfPresent(String.self, forKey: .joindate)
linklink_id = try values.decodeIfPresent(String.self, forKey: .linklink_id)
gps = try values.decodeIfPresent(Bool.self, forKey: .gps)
radius = try values.decodeIfPresent(Int.self, forKey: .radius)
showme = try values.decodeIfPresent(Bool.self, forKey: .showme)
}
}
struct Basket : Codable {
let mood : String?
let score : Int?
enum CodingKeys: String, CodingKey {
case mood = "mood"
case score = "score"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
mood = try values.decodeIfPresent(String.self, forKey: .mood)
score = try values.decodeIfPresent(Int.self, forKey: .score)
}
}
struct Customcolors : Codable {
let text : String?
let opacity : Double?
let bg : String?
enum CodingKeys: String, CodingKey {
case text = "text"
case opacity = "opacity"
case bg = "bg"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
opacity = try values.decodeIfPresent(Double.self, forKey: .opacity)
bg = try values.decodeIfPresent(String.self, forKey: .bg)
}
}
Here is my json response also
{"status":1,"message":"Fetched data","profile":{"name":"Prem Kumar","email":"a#a.com","verified":true,"phone":"+91998532542","articletype":["fun","article","insight"],"ai_score":100,"bfi":"100%","pic":"","cover":"","background":"","layout":"3column","customcolors":{"text":"#e7e7e7","opacity":"0.8","bg":"#272323"},"widgets":["interest","education","hobbies","media","habits","profession","weather","smabusinesstypes","orderhistory","newspaper","country","wishlist"],"basket":[{"mood":"sadness","score":5},{"mood":"fear","score":4},{"mood":"anger","score":4},{"mood":"disgust","score":3},{"mood":"hate","score":3},{"mood":"jealousy","score":2},{"mood":"satisfaction","score":0},{"mood":"competetive","score":0},{"mood":"frustration","score":0},{"mood":"joy","score":0},{"mood":"elevation","score":0},{"mood":"love","score":0},{"mood":"energetic","score":0}],"joindate":"2017-12-10T07:50:06.379Z","linklink_id":"5a435b0a5c23904f78b76542","gps":true,"radius":5,"showme":true}}
Please read the error messages. They are pretty clear
In Profile the value for key phone is String
In CustomColors the value for key opacity is String
Every value in double quotes is String, even "0", "1.0" and "false"
You can delete all coding keys and all initializers because they are provided implicitly. And do not declare everything carelessly as optional. Declare only those properties as optional whose corresponding key could be missing. This particular JSON works without any optional property
As #vadian rightly said, remove all the initializers and do not declare everything as optional straightaway. Identify the keys which can be missing in the response and declare only those keys as optionals. The JSON that you have provided does not require any optionals.
Just to be more clear, please check the following code
struct ProfileModelClass : Codable {
let status : Int
let message : String
let profile : Profile
}
struct Profile : Codable {
let name : String
let email : String
let verified : Bool
let phone : String
let articletype : [String]
let ai_score : Int
let bfi : String
let pic : String
let cover : String
let background : String
let layout : String
let customcolors : Customcolors
let widgets : [String]
let basket : [Basket]
let joindate : String
let linklink_id : String
let gps : Bool
let radius : Int
let showme : Bool
}
struct Customcolors : Codable {
let text : String
let opacity : String
let bg : String
}
struct Basket : Codable {
let mood : String
let score : Int
}