My app uses a codable struct like so:
public struct UserProfile: Codable {
var name: String = ""
var completedGame: Bool = false
var levelScores: [LevelScore] = []
}
A JSONEncoder()
has been used to save an encoded array of UserProfile
s to user defaults.
In an upcoming update, I'd like to add a new property to this UserProfile
struct. Is this possible in some way?
or would I need to create a new Codable struct that has the same properties plus one new property, and then copy all the values over to the new struct and then start using that new struct in place of anywhere that the UserProfile
struct was previously used?
If I simply add a new property to the struct, then I won't be able to load the previous encoded array of UserProfile
s as the struct will no longer have matching properties. When I get to this code for loading the saved users:
if let savedUsers = UserDefaults.standard.object(forKey: "SavedUsers") as? Data {
let decoder = JSONDecoder()
if let loadedUsers = try? decoder.decode([UserProfile].self, from: savedUsers) {
loadedUsers
doesn't decode if the properties that UserProfile
had when they were encoded and saved do not contain all the current properties of the UserProfile
struct.
Any suggestions for updating the saved struct properties? Or must I go the long way around and re-create since I didn't already plan ahead for this property to be included previously?
Thanks for any help!
As mentioned in the comments you can make the new property optional and then decoding will work for old data.
var newProperty: Int?
Another option is to apply a default value during the decoding if the property is missing.
This can be done by implementing init(from:)
in your struct
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
completedGame = try container.decode(Bool.self, forKey: .completedGame)
do {
newProperty = try container.decode(Int.self, forKey: .newProperty)
} catch {
newProperty = -1
}
levelScores = try container.decode([LevelScore].self, forKey: .levelScores)
}
This requires that you define a CodingKey
enum
enum CodingKeys: String, CodingKey {
case name, completedGame, newProperty, levelScores
}
A third option if you don't want it to be optional when used in the code is to wrap it in a computed non-optional property, again a default value is used. Here _newProperty
will be used in the stored json but newProperty
is used in the code
private var _newProperty: Int?
var newProperty: Int {
get {
_newProperty ?? -1
}
set {
_newProperty = newValue
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With