I am trying to convert the Landmarks app to SwiftData… why isn’t this class conforming to codable/decodable? It won’t compile but the messages are non-specific:
"Type ‘Landmark’ does not conform to protocol ‘Decodable’"
"Type ‘Landmark’ does not conform to protocol ‘Encodable’"
and
In expansion of macro ‘Model’ here:
"Cannot automatically synthesize ‘Decodable’ because ‘any SwiftData.BackingData’ does not conform to ‘Decodable’"
import Foundation
import SwiftUI
import CoreLocation
import SwiftData
@Model
final class Landmark: Hashable, Codable, Identifiable {
var id: Int
var name: String
var park: String
var state: String
var dezcription: String
var isFavorite: Bool
var isFeatured: Bool
var category: Category
private var coordinates: Coordinates
private var imageName: String
init(id:Int = Int.random(in: 2000...Int.max), name:String = "", park: String = "", state:String = "", dezcription: String = "", isFavorite: Bool = false, isFeatured:Bool = false, category: Category = Category.mountains, coordinates: Coordinates = Coordinates(latitude: 0, longitude: 0), imageName:String = "umbagog") {
self.id = id
self.name = name
self.park = park
self.state = state
self.dezcription = dezcription
self.isFavorite = isFavorite
self.isFeatured = isFeatured
self.category = category
self.coordinates = coordinates
self.imageName = imageName
}
enum Category: String, CaseIterable, Codable {
case lakes = "Lakes"
case rivers = "Rivers"
case mountains = "Mountains"
}
var image: Image {
Image(imageName)
}
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}
PS – I know that the design of this (the random int id… the default location of 0,0) are bad… but that’s not the point of the question.
I’ve tried:
- Commenting out/isolating potentially problematic types
- Making sure everything is codable
- Including all vars in the initializer
- Reading the dos and donts of declaring a @Model class
- Making the Image var @Transient (although it is computed anyway so that should not be necessary)
- Moving the Coordinates struct and Category enum higher in scope (out of this class)
- Adding a "real" UUID variable that actually is guaranteed unique
3
Answers
Sorry, this is a case where I made a false assumption in my question — That this class needs to be codable.
The reason the class was marked Codable was because that was the way it was in the original Landmarks app and I didnt think to change it.
What I didn't realize is that a class marked with @Model does not require any explicit protocols, and in fact should not be marked codable unless one plans on doing some serious surgery as in Sweeper's answer.
If I simply remove the Codable protocol (and can also remove all other protocols safely), it works.
I enterpreted the error message as a Swiftdata error when it is just a “normal” error saying “this class is not codable [because of Swiftdata macro]”
So the other previous answers are appreciated and valid if anyone out there actually has a class they need to be a swift data model and codable, but in my case it was just a false assumption.
One could say that this renders the question invalid, but that is hindsight and I think this the answer to my question, and the information will be valuable to people with the same misunderstanding.
The reason this happens is that the synthesis of the
Codable
implementation happens after the macros have expanded.After the macros have expanded, all the properties you declared become computed properties, and the macros add an additional
_$backingData
property, as well as an underscored-prefixed version of each property you declared, of type_SwiftDataNoType
(just an empty struct, acting as a placeholder), among other things.Basically,
@Model
turns this:into:
No wonder the
Codable
synthesis fails!I recommend checking out the actual code that it generates (the above is only a simplified version) with Xcode’s "Inline Macro" and "Expand Macro" context menu items.
You can conform to
Codable
manually, like this article shows. See also workingdog’s answer.Alternatively, try writing a macro that generates a
Codable
implementation. Your macro would receive the same syntax tree as@Model
, so you will have access to the class’ members before the macro expansion. You’d use it like this:Here is a rough sketch of such a
CodableModel
:Try this approach of using a specific
public init(from decoder: Decoder)
andpublic func encode(to encoder: Encoder)
to make yourLandmark
Codable.Note you cannot have
var image: Image
, Image is a View and should not be part of your model.Similarly,
var locationCoordinate: CLLocationCoordinate2D {...}
is not Codable, use a functioninstead, as shown in the example code.