Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
121 views
in Technique[技术] by (71.8m points)

Swift: Add property to a struct that gets encoded to JSON

I have a Struct for the user's preferences and this object gets encoded and stored as a JSON file and stored for iCloud Document Sync (I found this to be more reliable than NSUbiquitousContainer, even though that's what you're supposed to use for preferences).

Let's say in v1 of the app the struct looks like this and all users have a JSON file with these 3 properties.

struct Preferences: Codable {
    var soundsDisabled: Bool = false
    var hapticsDisabled: Bool = false
    var badgeNumberEnabled: Bool = false
}

What I need to figure out is if there is a way to add a 4th property in v1.1 to this Struct without messing up the existing JSON. Right now if I do add a new property, everything gets reset to the default values and the user's preferences get lost.

UPDATE: this is how this struct gets encoded to JSON

extension Storage {
    
    static var preferences: Preferences {
        get {
            guard
                let data = try? Data(contentsOf: Storage.preferencesFile),
                let decoded = try? JSONDecoder().decode(Preferences.self, from: data)
            else { return Preferences() }
            return decoded
        }
        set {
            do { try JSONEncoder().encode(newValue).write(to: Storage.preferencesFile, options: .atomic) }
            catch let error {
                print("Failed to write Preferences: (error.localizedDescription)")
                MSAnalytics.trackEvent("Failed to write preferences", withProperties: ["Error": error.localizedDescription])
            }
        }
    }

}

Thank you.

question from:https://stackoverflow.com/questions/65640994/swift-add-property-to-a-struct-that-gets-encoded-to-json

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This probably happens because JSONDecoder cannot decode the stored data as a Preferences instance any more. (and in that case you are returning a new instance Preferences())

What you can do is to add your new property as an optional, like this:

struct Preferences: Codable {
    var soundsDisabled: Bool = false
    var hapticsDisabled: Bool = false
    var badgeNumberEnabled: Bool = false
    var newProperty: Bool? = false
}

Then after decoding the old data, newProperty will be nil.

Update: Another, maybe better, solution would be to implement decoding yourself and when there is no property newProperty set the default value:

struct Preferences: Codable {
    var soundsDisabled: Bool = false
    var hapticsDisabled: Bool = false
    var badgeNumberEnabled: Bool = false
    var newProperty: Bool = false
    
    enum CodingKeys: String, CodingKey {
        case soundsDisabled
        case hapticsDisabled
        case badgeNumberEnabled
        case newProperty
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        soundsDisabled = try container.decode(Bool.self, forKey: .soundsDisabled)
        hapticsDisabled = try container.decode(Bool.self, forKey: .hapticsDisabled)
        badgeNumberEnabled = try container.decode(Bool.self, forKey: .badgeNumberEnabled)

        // Decode newProperty and if not present set default value
        newProperty = try container.decodeIfPresent(Bool.self, forKey: .newProperty) ?? false
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...