Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Container nil value custom decoder Swift

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?

like image 840
Simon McNeil Avatar asked Jul 28 '26 08:07

Simon McNeil


2 Answers

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.

like image 95
George Avatar answered Jul 30 '26 21:07

George


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

}

like image 24
Simon McNeil Avatar answered Jul 30 '26 22:07

Simon McNeil