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)
}
Related
I am trying to decode the following json object to my User model in Swift.
My issue is decoding the values _id and token out of the tokens array, where the first token in the array contains the values I want to decode into User.tokenId and User.token.
I am trying to extract/map the values directly into my User model struct without having another nested struct in my User model ( such as struct Token { var id: String , var token: String } )
let json = """
{
"currentLocation": {
"latitude": 0,
"longitude": 0
},
"profileImageUrl": "",
"bio": "",
"_id": "601453e4aae564fc19075b68",
"username": "johnsmith",
"name": "john",
"email": "johnsmith#gmail.com",
"keywords": ["word", "weds"],
"tokens": [
{
"_id": "213453e4aae564fcqu775b69",
"token": "eyJhbGciOiJIUzqoNiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDE0NTNlNGFhZTU2NGZjMTkwNzViNjgiLCJpYXQiOjE2MTE5NDQ5MzIsImV4cCI6MTYxMjM3NjkzMn0.PbTsA3B0MAfcVvEF1UAMhUXFiqIL1FcxVFGgMTZ5HCk"
}
],
"createdAt": "2021-01-29T18:28:52.845Z",
"updatedAt": "2021-01-29T18:28:52.883Z"
}
""".data(using: .utf8)!
struct User: Codable {
var latitude: Double
var longitude: Double
var profileImageUrl: String
var bio: String
var userId: String
var username: String
var name: String
var email: String
var keywords: [String]
var tokenId: String
var token: String
var createdAt: Date
var updatedAt: Date
private enum UserKeys: String, CodingKey {
case currentLocation
case profileImageUrl
case bio
case userId = "_id"
case username
case name
case email
case keywords
case tokens
case createdAt
case updatedAt
}
private enum CurrentLocationKeys: String, CodingKey {
case latitude
case longitude
}
private enum TokenKeys: String, CodingKey {
case tokenId = "_id"
case token
}
init(from decoder: Decoder) throws {
let userContainer = try decoder.container(keyedBy: UserKeys.self)
let currentLocationContainer = try userContainer.nestedContainer(keyedBy: CurrentLocationKeys.self, forKey: .currentLocation)
self.latitude = try currentLocationContainer.decode(Double.self, forKey: .latitude)
self.longitude = try currentLocationContainer.decode(Double.self, forKey: .longitude)
self.profileImageUrl = try userContainer.decode(String.self, forKey: .profileImageUrl)
self.bio = try userContainer.decode(String.self, forKey: .bio)
self.userId = try userContainer.decode(String.self, forKey: .userId)
self.username = try userContainer.decode(String.self, forKey: .username)
self.name = try userContainer.decode(String.self, forKey: .name)
self.email = try userContainer.decode(String.self, forKey: .email)
self.keywords = try userContainer.decode([String].self, forKey: .keywords)
let tokensContainer = try userContainer.nestedContainer(keyedBy: TokenKeys.self, forKey: .tokens)
self.tokenId = try tokensContainer.decode(String.self, forKey: .tokenId)
self.token = try tokensContainer.decode(String.self, forKey: .token)
self.createdAt = try userContainer.decode(Date.self, forKey: .createdAt)
self.updatedAt = try userContainer.decode(Date.self, forKey: .updatedAt)
}
}
let user = try! decoder.decode(User.self, from: json)
First of all I assume that your decoder has an appropriate date decoding strategy to be able to decode the ISO8601 strings to Date.
The enclosing container of the token dictionary is an array. You have to insert an intermediate nestedUnkeyedContainer
...
var arrayContainer = try userContainer.nestedUnkeyedContainer(forKey: .tokens)
let tokensContainer = try arrayContainer.nestedContainer(keyedBy: TokenKeys.self)
self.tokenId = try tokensContainer.decode(String.self, forKey: .tokenId)
self.token = try tokensContainer.decode(String.self, forKey: .token)
...
It's much less code to decode the JSON into multiple structs
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
This question already has answers here:
Swift 4 Codable - Bool or String values
(1 answer)
Swift 4 Codable - API provides sometimes an Int sometimes a String
(1 answer)
Closed 2 years ago.
I have a json which looks like so...
{
id = 123456;
isDeleted = 0;
name = testName;
parentId = "<null>"; // This can also be integer
plantId = 1223; // This can also be string
type = 1;
}
In the response above, I can get either a string or an int for both parentId & plantId. How can I handle both the cases..?
This is how my structure looks...
struct Root : Decodable {
let organizations : [Organization1]
}
struct Organization1 : Decodable {
let id: Int
let isDeleted: Bool
let name: String
let parentId: Int?
let type: Int
let plantId: String?
let loggedInUserId: Int?
}
You can do that like this
struct GeneralProduct: Decodable {
let id: Int
let isDeleted: Bool
let name: String
let parentId: String?
let type: Int
let plantId: String?
let loggedInUserId: Int?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
isDeleted = try container.decode(Bool.self, forKey: .isDeleted)
name = try container.decode(String.self, forKey: .id)
type = try container.decode(Int.self, forKey: .type)
loggedInUserId = try container.decode(Int.self, forKey: .loggedInUserId)
if let value = try? container.decode(Int.self, forKey: .parentId) {
parentId = String(value)
} else {
parentId = try container.decode(String.self, forKey: .id)
}
if let value = try? container.decode(Int.self, forKey: .plantId) {
plantId = String(value)
} else {
plantId = try container.decode(String.self, forKey: .id)
}
}
}
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)
}
}
}
}
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
}