I have a type which represents an ID. This ID comes from a backend as an untyped string. The Swift type looks something like this:
struct TypedID: Codable {
init(_ string: String) {
stringValue = string
}
init(from decoder: any Decoder) throws {
stringValue = try decoder.singleValueContainer().decode(String.self)
}
func encode(to encoder: any Encoder) throws {
try stringValue.encode(to: encoder)
}
let stringValue: String
}
If I use this type in a SwiftData @Model
, or even as a property of another Codeable
struct
which is contained in the @Model
, SwiftData fails to create instances of my model and emits the following error:
CoreData: error: CoreData: error: Row (pk = #) for entity '<@Model type>' is missing mandatory text data for property 'stringValue'
This issue goes away if I remove the custom implementation of init(from:)
and encode(to:)
, but doing so makes the coded representation of my type an object instead of a string, which is not what I want.
/// JSON without custom implementations:
{
"stringValue": "<the ID>"
}
/// JSON with custom implementations:
"<the ID>"
Is there anything I can do to be able to serialize a Codable
type with a custom coding implementation like this to a SwiftData model?
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...