Neph
Neph

Reputation: 2001

Optional value in Codable is nil

My Swift 5 (Xcode 11.5) app saves people in a text file (json format).

The structs I use to decode the json text:

struct People:Codable {
    var groupName:String
    var groupLocation:String 
    var members:[Person]

    init(_ gn:String,_ gl:String,_ m:[Person] {
        groupName = gn
        groupLocation = gl
        members = m
}

struct Person:Codable {
    var name:String
    var age:Int
    var profession:String?

    init(_ n:String,_ a:Int,_ p:String?) {
        name = n
        age = a
        profession = (p==nil) ? "none" : p!
    }
}

Decoding the contents of the text file:

let jsonData = Data(text.utf8)
let decoder = JSONDecoder()
let people = try decoder.decode(People.self, from: jsonData)
let members = people.members

for (i,member) in members.enumerated() {
    //print(member.profession==nil)
    //Do stuff
}

profession was added later on and there also might be more values added in the future but I want my app to be backwards compatible to older files. If profession doesn't exist, it should use "none" instead (see code above) but when I check member.profession after decoding, it's still nil. name, age,... all contain the right values, so that part works.

How do I give profession a value in the struct if it doesn't exist in the json file? What's the simplest/cleanest way to do this, so I can also add to it later on, if necessary (e.g. birthday, gender,...)?

Upvotes: 0

Views: 1761

Answers (1)

PGDev
PGDev

Reputation: 24341

If you need to parse your JSON in some way other than what Codable already does, you need to implement the custom initializer init(from:), i.e.

struct Person:Codable {
    var name:String
    var age:Int
    var profession: String
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.age = try container.decode(Int.self, forKey: .age)
        if let profession = try container.decodeIfPresent(String.self, forKey: .profession) {
            self.profession = profession
        } else {
            self.profession = "none"
        }
    }
}

Also, since you're always giving a value to profession, no need to make it optional.

Upvotes: 2

Related Questions