SwiftData: Failed to decode a composite attribute

I changed an enum value from this:

enum Kind: String, Codable, CaseIterable {
        case credit
    }

to this:

enum Kind: String, Codable, CaseIterable {
        case credit = "Credit"
    }

And now it fails to load the data. This is inside of a SwiftData model. I get why the error is occurring, but is there a way to resolve this issue without having to revert back or delete the data?

Error: dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "Cannot initialize Kind from invalid String value credit", underlyingError: nil))

Answered by joadan in 813898022

A migration is probably the best solution but if you don't want to do that you could use a custom decoding for the enum

extension Kind {
    init(from decoder: any Decoder) throws {
        let container = try decoder.singleValueContainer()
        let rawValue = try container.decode(String.self)
        if let kind = Kind(rawValue: rawValue) {
            self = kind
        } else {
            let oldValue = rawValue.capitalized // <-- You might need to adjust this depending on how the rest of the enum looks.
            guard let kind = Kind(rawValue: oldValue) else {
                throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Unknow kind: \(oldValue)"))
            }
            self = kind
        }
    }
}

Note that this will fix the value when read from storage but to actually store the new value, "Credit", each objects will need to be saved until it's persisted.

You can try this:

enum Kind: String, Codable, CaseIterable, Identifiable {
    case credit = "Credit"

    var id:String { rawValue }
}

If that not work. I think you need to use SchemaMigrationPlan and translate model to other version model.

There is a example for SchemaMigrationPlan. example.

I hope these will be useful to you.

Hi @Xavier-k , well unfortunately in SwiftData (CoreData for old man as me) things are not so simple.

What you have done is change on the model that is used to generate the entity relationships based on SwiftData modelling.

What I would like to suggest, even if the modification on model is so simple, is to use a migration strategy from old to new model. For example, this is a great article on argument of SwiftData migration https://www.hackingwithswift.com/quick-start/swiftdata/how-to-create-a-complex-migration-using-versionedschema . SwiftData is not simple...

Bye Rob

Accepted Answer

A migration is probably the best solution but if you don't want to do that you could use a custom decoding for the enum

extension Kind {
    init(from decoder: any Decoder) throws {
        let container = try decoder.singleValueContainer()
        let rawValue = try container.decode(String.self)
        if let kind = Kind(rawValue: rawValue) {
            self = kind
        } else {
            let oldValue = rawValue.capitalized // <-- You might need to adjust this depending on how the rest of the enum looks.
            guard let kind = Kind(rawValue: oldValue) else {
                throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Unknow kind: \(oldValue)"))
            }
            self = kind
        }
    }
}

Note that this will fix the value when read from storage but to actually store the new value, "Credit", each objects will need to be saved until it's persisted.

SwiftData: Failed to decode a composite attribute
 
 
Q