So I have this simple json response:
{
"gender": "male",
"name": {
"title": "mr",
"first": "brad",
"last": "gibson"
}
}
And this is my customer decoder:
struct UserModel: Decodable {
var gender: String
var title: String?
var first: String?
var last: String?
// Top-level coding keys
enum CodingKeys: String, CodingKey {
case name, gender
}
enum NameKeys: CodingKey {
case title, first, last
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
gender = try container.decode(String.self, forKey: .gender)
let name = try container.nestedContainer(keyedBy: NameKeys.self, forKey: .name)
title = try name.decodeIfPresent(String.self, forKey: .title)
first = try name.decodeIfPresent(String.self, forKey: .first)
last = try name.decodeIfPresent(String.self, forKey: .last)
}
}
I know how to handle the error scenarios if one of the keys inside the nested object name (title, first, last) isn't sent. My question is how would I write something to prevent a crash if the "name" object is not sent back from the response?
CodePudding user response:
How about using below?
struct Name: Decodable {
var title: String?
var first: String?
var last: String?
}
struct UserModel: Decodable {
var name: Name?
var gender: String
}
Then everything else gets taken care of itself. For e.g all of below will be parsed without issues.
{
"gender": "male",
"name": {
"title": "mr",
"first": "brad",
"last": "gibson"
}
}
{
"gender": "male"
}
{
"gender": "male",
"name": {
"title": "mr",
"first": "brad"
}
}
I tested using below
let str = """
{
"gender": "male",
"name": {
"title": "mr",
"first": "brad"
}
}
"""
let usermodel = try! JSONDecoder().decode(UserModel.self, from: str.data(using: .utf8)!)
print(usermodel)
CodePudding user response:
You can have nested Codable
structs, as such:
struct User: Codable {
let gender: String
let name: UserName?
}
struct UserName: Codable {
let title: String?
let first: String?
let last: String?
}
To make the syntax better than user.name?.title
, we can change this to just user.title
with the use of @dynamicMemberLookup
. Change User
to this:
@dynamicMemberLookup
struct User: Codable {
let gender: String
let name: UserName?
subscript<T>(dynamicMember k: KeyPath<UserName, T>) -> T? {
name?[keyPath: k]
}
subscript<T>(dynamicMember k: KeyPath<UserName, T?>) -> T? {
name?[keyPath: k]
}
}
Now, you can do the short-hand form to get each property. This now mimics your single container with all the properties.
CodePudding user response:
Thanks to Deepika, this is the solution:
struct UserModel: Decodable {
var gender: String
var title: String?
var first: String?
var last: String?
// Top-level coding keys
enum CodingKeys: String, CodingKey {
case name, gender
}
enum NameKeys: CodingKey {
case title, first, last
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
gender = try container.decode(String.self, forKey: .gender)
if container.contains(.name) {
let name = try container.nestedContainer(keyedBy: NameKeys.self, forKey: .name)
title = try name.decode(String.self, forKey: .title)
first = try name.decode(String.self, forKey: .first)
last = try name.decode(String.self, forKey: .last)
}
}
}