I found the answer in this post: https://forums.developer.apple.com/forums/thread/715338
Post
Replies
Boosts
Views
Activity
The primary issue isn't with the JSON encoding but with SwiftData (and specifically a model defined using @Model).
RawRepresentable hits the same issue I described. If you do the standard "make a macOS app with SwiftData" flown in a recent Xcode and change the definition of Item to the following, the issue is reproducable
struct Problematic1: Codable {
init(from decoder: any Decoder) throws {
self.stringValue = try decoder.singleValueContainer().decode(String.self)
}
enum CodingKeys: CodingKey {
case stringValue
}
func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(stringValue)
}
init(stringValue: String) {
self.stringValue = stringValue
}
var stringValue: String
}
struct Problematic2: RawRepresentable, Codable {
init(rawValue: String) {
self.rawValue = rawValue
}
var rawValue: String
}
@Model
final class Item {
var timestamp: Date
var oneOptional: Problematic1? = Problematic1(stringValue: "1")// <-- OK
var one: Problematic1 = Problematic1(stringValue: "1") // <-- Causes issue
var twoOptional: Problematic2? = Problematic2(rawValue: "2") // <-- OK
var two: Problematic2 = Problematic2(rawValue: "2") // <-- Causes issue
init(timestamp: Date) {
self.timestamp = timestamp
}
}
Hmm It looks like the issue is SwiftData abuses Codable (i.e. doesn't just use encode(to:) and init(from:) and the way in which it does so (which seems to include using reflection) is not robust to custom implementations of those methods.
Some of the nuance is covered by this blog post: https://fatbobman.com/en/posts/considerations-for-using-codable-and-enums-in-swiftdata-models/
That blog post also describes a trick where you can nudge SwiftData into actually using the Codable protocol correctly by storing the codable property in an Array (which did work around my issue) though it is unclear if this will be robust to future "improvements" to SwiftData...