Nested Json Decoder Swift - ios

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
}

Related

Filtering empty values from an API in swift

I'm trying to filter out empty and null values from an api in a json format in swift(UIKit).
The full data returns look like below but sometimes can contain null or empty values in the characteristic key. There is always going to be the same amount of keys.
//Cat
{
"breedname": "Persian",
"picture": "https://catimage.random.png",
"characteristic1": "Shy",
"characteristic2": "Hungry all the time"
"characteristic3": "Likes apples"
"characteristic4": "Grey color"
"characteristic5": "likes chin scratches"
}
{
"breedname": "Bengal",
"picture": "https://catimage.random.png",
"characteristic1": "Active",
"characteristic2": "Adventurous"
"characteristic3": ""
"characteristic4": ""
"characteristic5": ""
}
{
"breedname": "ragdoll",
"picture": "https://catimage.random.png",
"characteristic1": "Fiestey",
"characteristic2": "sharp claws"
"characteristic3": null
"characteristic4": null
"characteristic5": null
}
In order to filter null and empty values before showing in the UI, I have a Decodable class like below and a custom init class with the decodeifPresent method which changes null values to nill. However for empty values I just created a method which converts empty string values to nill. I'm not sure if there are better ways to handle empty and null data and filtering them out? I refer to all the Decodable keys in the UI so I cannot simply delete the keys themselves.
struct Cat: Decodable {
let breedName: String
let picture: String
let characteristic1 : String?
let characteristic2 : String?
let characteristic3 : String?
let characteristic4 : String?
let characteristic5 : String?
enum CodingKeys: String, CodingKey {
case breedName
case picture
case characteristic1
case characteristic2
case characteristic3
case characteristic4
case characteristic5
}
func checkEmpty(s: String?) -> String? {
if s == "" {
return nil
}
return s
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.breedName= try container.decode(String.self, forKey: .breedName)
self.picture = try container.decode(String.self, forKey: .picture)
self.characteristic1 = try container.decodeIfPresent(String.self, forKey: .characteristic1)
self.characteristic2 = try container.decodeIfPresent(String.self, forKey: .characteristic2)
self.characteristic3 = try container.decodeIfPresent(String.self, forKey: .characteristic3)
self.characteristic4 = try container.decodeIfPresent(String.self, forKey: .characteristic4)
self.characteristic5 = try container.decodeIfPresent(String.self, forKey: .characteristic5)
self.characteristic1 = checkEmpty(s: self.characteristic1)
self.characteristic2 = checkEmpty(s: self.characteristic2)
self.characteristic3 = checkEmpty(s: self.characteristic3)
self.characteristic4 = checkEmpty(s: self.characteristic4)
self.characteristic5 = checkEmpty(s: self.characteristic5)
One solution is to check for empty in a function defined in an extension to String
extension String {
func emptyAsNil() -> String? {
self.isEmpty ? nil : self
}
}
Then you could do all in one step in the init
self.characteristic1 = try container.decodeIfPresent(String.self, forKey: .characteristic1)?.emptyAsNil()
But perhaps a better solution is to gather all those properties in a collection like an array or a dictionary. Here I have chosen an array
struct Cat: Decodable {
let breedName: String
let picture: String
var characteristics: [String]
}
and then in the init we add only non-nil, non-empty values to the array
if let value = try container.decodeIfPresent(String.self, forKey: .characteristic1), !value.isEmpty {
characteristics.append(value)
}
or another way is to loop over the keys
let keys: [CodingKeys] = [.characteristic1,
.characteristic2,
.characteristic3,
.characteristic4,
.characteristic5]
for key in keys {
if let value = try container.decodeIfPresent(String.self, forKey: key), !value.isEmpty {
characteristics.append(value)
}
}

Decoding fails if keys not present

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

Can I use both init() and initial value in Decoder?

I have lots of values in my codable struct. I have URLs coming in as ""(empty string) so I need custom Decoder to convert "" as nil. So I made a propertyWrapper to solve this.
For example, I have values like ["https://google.com", "", "https://google.com"] and I want to make it as [URL?]. This works well with my decoder. It's converted as [URL("https://google.com"), nil, URL("https://google.com")]
However, I found a problem that, when I use init(from decoder: Decoder) throws, I also have to initialize all other values in struct. Is there any way to use just courseImages and use other values in struct as set?
struct CabinetCourse: Codable {
let courseId: String
let title: String
let planStartDate: Date
let planEndDate: Date
let companionTypeCd: String
let courseCategory: String
let planId: String
let nickname: String?
let isCabinet: Bool
let isFavorite: Bool?
let course: String
let score: String
let shareCnt: Int
let favoriteCnt: Int
let cabinetCnt: Int
let placeCount: Int
let createDt: Date
let childPlaceCount: Int
let wheelChairPlaceCount: Int
let elderPlaceCount: Int
#OptionalObject
var courseImages: [URL?]
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let emptyURLS = try values.decode([OptionalObject<URL>].self, forKey: .courseImages)
courseImages = emptyURLS.map { $0.wrappedValue }
// => these are the lines I don't want to write
courseId = try values.decode(String.self, forKey: .courseId)
title = try values.decode(String.self, forKey: .title)
planStartDate = try values.decode(Date.self, forKey: .planStartDate)
planEndDate = try values.decode(Date.self, forKey: .planEndDate)
companionTypeCd = try values.decode(String.self, forKey: .companionTypeCd)
courseCategory = try values.decode(String.self, forKey: .courseCategory)
planId = try values.decode(String.self, forKey: .planId)
nickname = try values.decode(String.self, forKey: .nickname)
isCabinet = try values.decode(Bool.self, forKey: .isCabinet)
isFavorite = try values.decode(Bool.self, forKey: .isCabinet)
course = try values.decode(String.self, forKey: .isCabinet)
score = try values.decode(String.self, forKey: .isCabinet)
}
#propertyWrapper
struct OptionalObject<Base: Decodable>: Decodable {
var wrappedValue: Base?
init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Base.self)
} catch {
wrappedValue = nil
}
}
}
You are misunderstanding property wrappers. They "decorate" the entire property type - [URL?], not just the array element type URL?. [URL?] doesn't match the type of wrappedValue. [URL]? does (Base == [URL]), but that's not what you want.
One way to create a property wrapper that can be applied to an array of optionals is:
#propertyWrapper
struct OptionalArray<Base: Decodable>: Decodable {
var wrappedValue: [Base?]
...
Now Base == URL matches [URL?], and in init, you have to decode a [Base?]:
init(from decoder: Decoder) throws {
var arr = [Base?]()
var container = try decoder.unkeyedContainer()
for _ in 0..<(container.count ?? 0) {
if let element = try? container.decode(Base.self) {
arr.append(element)
} else {
arr.append(nil)
_ = try container.decode(String.self) // advances the decoder to the next position
}
}
wrappedValue = arr
}
Once you have the property wrapper, you don't need the custom decoding code at all. Swift figures it out.
struct CabinetCourse: Decodable {
let courseId: String
let title: String
let planStartDate: Date
let planEndDate: Date
let companionTypeCd: String
let courseCategory: String
let planId: String
let nickname: String?
let isCabinet: Bool
let isFavorite: Bool?
let course: String
let score: String
let shareCnt: Int
let favoriteCnt: Int
let cabinetCnt: Int
let placeCount: Int
let createDt: Date
let childPlaceCount: Int
let wheelChairPlaceCount: Int
let elderPlaceCount: Int
#OptionalArray
var courseImages: [URL?]
}
// That's it!
For the encoding part, it depends on how you want to encode the nils. But either way, the code is very similar to the decoding code.

How to construct Codable from response data structure

How to do codable for Any & JSONNull from response data
Json Response Data
"userType": {
"id": "5f18J20a21n",
"name": "userName",
"name_prefix": null,
"group": []
}
UserType Model Class
struct UserType : Codable {
let id : String?
let name : String?
let namePrefix: JSONNull? // I replace JSONNull with String, Is it good way to do so?
let group : [JSONAny?]?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case namePrefix = "name_prefix"
case group = "group"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id)
name = try values.decodeIfPresent(String.self, forKey: .name)
namePrefix = try values.decodeIfPresent(String.self, forKey: . namePrefix)
// namePrefix = try values.decodeIfPresent(JSONNull.self, forKey: . namePrefix)
group = try values.decodeIfPresent([JSONAny].self, forKey: .group)
// Type of expression is ambiguous without more context
}
}
I try above code which gives an error
Type of expression is ambiguous without more context

How to generate swift class or object from XML or Json when build project in Xcode

I have a json file with the format like this:
{
"id":"nav_grap",
"startDestination":0,
"navigators":[
{
"id":0,
"groupsId":"0",
"controller_name":"LoginScreen",
"titleVi":"login_screen",
"titleEn":"Dang nhap",
"actions":[{
"name":"idaction_loginScreen_to_homeScreen",
"destination":1
}]},
{
"id":1,
"groupsId":"1",
"controller_name":"HomeScreen",
"titleVi":"Cong viec",
"titleEn":"Jobs",
"actions":[]
},
{
"id":2,
"groupsId":"2",
"controller_name":"NewsScreen",
"titleVi":"Tin tuc",
"titleEn":"News",
"actions":[]
},
{
"id":3,
"groupsId":"3",
"controller_name":"BiometricsScreen",
"titleVi":"Sin trac hoc",
"titleEn":"News",
"actions":[]
},
{
"id":4,
"groupsId":"4",
"controller_name":"ContactScreen",
"titleVi":"Tin tuc",
"titleEn":"News",
"actions":[]
}]
}.
I want to generate a class or object base on the format of this json file when I build my project so after project built I would have a object with properties like this:
class ScreenConfig : Decodable{
var id : String
var startDestination : Int
var navigators : [Navigator] = []
init(id : String, startDestination : Int, navigator : [Navigator]) {
self.id = id
self.startDestination = startDestination
self.navigators = navigator
}
init() {
self.id = ""
self.startDestination = 0
self.navigators = []
}
}.
So,could anyone please tell me how can I archive this? thanks.
I guess you are finding quicktype-xcode
quicktype-xcode: Creating models base on API JSON responses
To do it manually, you can use Sourcery or SwiftGen, which can automate daily development processes
Using the http://www.jsoncafe.com
struct TestingTest : Codable {
let id : String?
let navigators : [TestingNavigator]?
let startDestination : Int?
enum CodingKeys: String, CodingKey {
case id = "id"
case navigators = "navigators"
case startDestination = "startDestination"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id)
navigators = try values.decodeIfPresent([TestingNavigator].self, forKey: .navigators)
startDestination = try values.decodeIfPresent(Int.self, forKey: .startDestination)
}
}
struct TestingNavigator : Codable {
let actions : [AnyObject]?
let controllerName : String?
let groupsId : String?
let id : Int?
let titleEn : String?
let titleVi : String?
enum CodingKeys: String, CodingKey {
case actions = "actions"
case controllerName = "controller_name"
case groupsId = "groupsId"
case id = "id"
case titleEn = "titleEn"
case titleVi = "titleVi"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
actions = try values.decodeIfPresent([AnyObject].self, forKey: .actions)
controllerName = try values.decodeIfPresent(String.self, forKey: .controllerName)
groupsId = try values.decodeIfPresent(String.self, forKey: .groupsId)
id = try values.decodeIfPresent(Int.self, forKey: .id)
titleEn = try values.decodeIfPresent(String.self, forKey: .titleEn)
titleVi = try values.decodeIfPresent(String.self, forKey: .titleVi)
}
}
You need to write a Xcode plugin for generating JSON to Swift code.
So you should build extensions to the source editor in Xcode using XcodeKit.
Source editor extensions can read and modify the contents of a source file, as well as read and modify the current text selection within the editor.
struct ScreenConfig: Decodable{
let id : String
let startDestination : Int
let navigators : [Navigator]
}
struct Navigator: Decodable {
let id: Int
let groupsId: String
let controller_name : String
let titleVi: String
let titleEn: String
let actions: [Action]?
}
struct Action: Decodable {
let name: String
let destination: Int
}
Test Case:
you have a json file named "one.json" in your project bundle, which is configured with your data above
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let src = Bundle.main.url(forResource: "one", withExtension: "json"){
do {
let data = try Data(contentsOf: src)
let decoder = JSONDecoder()
let model = try decoder.decode(ScreenConfig.self, from: data)
print(model.navigators.first?.controller_name ?? "ha ha")
} catch let error {
print(error.localizedDescription)
}
}
}
}

Resources