Open sindresorhus opened 5 months ago
You save data with it, but later on, you add a RawRepresentable conformance. The previous persisted version of the struct can no longer be loaded as it's saved using the Codable bridge.
For this case, we could consider enhancing the RawRepresentableCodableBridge.
Since its Value
conforms to both Codable
and RawRepresentable
, we could test which deserialization can be loaded.
If the user switches the protocol from Codable to RawRepresentable, there may be a need for some migration.
Since its Value conforms to both Codable and RawRepresentable, we could test which deserialization can be loaded.
A value could potentially be loadable by both. This creates ambiguity and potential data-loss.
If the user switches the protocol from Codable to RawRepresentable, there may be a need for some migration.
Yes, that's a different case and unrelated to this issue.
A value could potentially be loadable by both. This creates ambiguity and potential data-loss.
How about change the deserialize
function in RawRepresentableCodableBridge
to this?
public func deserialize(_ object: Serializable?) -> Value? {
// Check whether it can deserialize with `CodableBridge`
if
let jsonString = object as? String,
let value = try? Value(jsonString: jsonString)
{
return value
}
guard let object = object as Value.RawValue else {
return nil
}
return Value(rawValue: object)
}
It will accept serialization values from both CodableBridge
and RawRepresentableBridge
.
To clarify my point. The potential problem is that the raw serialized value could be a string from a RawRepresentable serialization, but when deserialized using Codable it turns into a different value. I don't think we should guess the user's intent. It's better to be explicit about what you want.
In addition, trying to use Codable each time deserialize()
is called would add a lot of unnecessary overhead, since Codable is slow.
Get your point.
I think we might need to allow the user to choose the Bridge
they want to use for serialization and deserialization.
Here are the few solutions I come up:
Defaults
, and expose the internal bridge publicly to allow users to choose it.private enum FixtureCodableEnum: String, Hashable, Codable, Defaults.Serializable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
extension FixtureCodableEnum {
typealias Bridge = Defaults.RawRepresentableCodableBridge<Self>;
}
Defaults.Key.init
and issue an Xcode warning advising users to utilize Defaults.Serializable.RawRepresentable
instead.// in `Defaults.Key.init`
if defaultValue is Codable, defaultValue is any RawRepresentable {
runtimeWarn(false, "Please consider using `Defaults.Serializable.RawRepresentable` for serialization/deserialization stability")
}
Why not?
private enum FixtureCodableEnum: String, Hashable, Codable, Defaults.Serializable.RawRepresentable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
Why not?
Yeah, of course we can do this, but we might need to warn user when they are using ambiguous bridge to do the serialization and deserialization. With this user will need to specify the bridge they want to use.
I think 2
would be the best choice.
I think
2
would be the best choice.
Got it! Many thanks!
With option 2
, I think there might be a need to also having a Defaults.Serializable.Codable
?
With option 2, I think there might be a need to also having a Defaults.Serializable.Codable?
Yes. And Defaults.Serializable.NSSecureCoding
.
Current:
I think this may be better:
This is shorter, but more importantly, it makes the choice explicit. Users should prefer this over
Defaults.Serializable
.Defaults.Serializable
has a big flaw. For example, you have astruct X: Codable, Defaults.Serializable
. You save data with it, but later on, you add aRawRepresentable
conformance. The previous persisted version of the struct can no longer be loaded as it's saved using theCodable
bridge, but now tries to load using theRawRepresentable
bridge.// @hank121314 Thoughts? Any better way to prevent this problem?