Reading Codable properties raised a fatal error.

Using SwiftData, I have a model that uses an enum as a property and a custom Codable type

import SwiftData
import CoreLocation

// MARK: - Main Model
@Model
class Cache {
    var size: CacheSize?
    var coordinates: CLLocationCoordinate2D
}

// MARK: - CacheSize Enum
enum CacheSize: CaseIterable, Codable {
    case nano
    case micro
    case small
    case regular
    case large
    case veryLarge
    case notChosen
    case virtual
    case other
}

// MARK: - Codable for CLLocationCoordinate2D
extension CLLocationCoordinate2D: Codable {
    enum CodingKeys: String, CodingKey {
        case lat, lng
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(self.latitude, forKey: .lat)
        try container.encode(self.longitude, forKey: .lng)
    }
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        let latitude = try container.decode(CLLocationDegrees.self, forKey: .lat)
        let longitude = try container.decode(CLLocationDegrees.self, forKey: .lng)
        
        self.init(latitude: latitude, longitude: longitude)
    }
}

When I fetch the object from the ModelContext and try to access the property corresponding to this enum, I have a fatal error raised in BackingData.swift:

SwiftData/BackingData.swift:197: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(iGeo.CacheSize, Swift.DecodingError.Context(codingPath: [], debugDescription: "Invalid number of keys found, expected one.", underlyingError: nil))

When I try to read the CLLocationCoordinates2D property, I have also a crash in the Decodable init(from decoder: Decoder implementation when trying to read the first value from the container.

Did I miss something here or did something wrong?

Post not yet marked as solved Up vote post of yageekCH Down vote post of yageekCH
1.2k views

Replies

Better to store your own location type and transform it into a CLLocationCoordinate2D when you need one. If you're using SwiftUI you can use the View hierarchy to transform from your model type to that type, via a computed property called from body. That way it will only be transformed when it has changed.

This could be a possibility 😅 But as the WWDC video and documentation mentioned that struct should be supported, I wanted to know how it is expected to work with SwiftData.

It seems that SwiftData and Codable model properties are seriously broken (at least as of Xcode 15.0 beta 3). I'm getting the same error shown in the above question for any model property whose type is anything marked as Codable. I am finding that in many cases the custom encode method is never called when storing a value in an instance of the model. And when reading the property, the init(from:) isn't encoded as expected.

I was able to get things to work for CLLocationCoordinate2D with the following implementation:

extension CLLocationCoordinate2D: Codable {
    // These exact case values must be used to decode a location
    enum CodingKeys: CodingKey {
        case latitude
        case longitude
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let lat = try container.decode(CLLocationDegrees.self, forKey: .latitude)
        let lon = try container.decode(CLLocationDegrees.self, forKey: .longitude)

        self = CLLocationCoordinate2D(latitude: lat, longitude: lon)
    }

    // Never called by SwiftData
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.latitude, forKey: .latitude)
        try container.encode(self.longitude, forKey: .longitude)
    }
}