1

I'm encountering an error when trying to save a SwiftData entity with a transformable attribute that should handle an array of strings. The error occurs specifically when the array is empty.

The Error

Thread 1: "Unacceptable type of value for attribute: property = \"categories\"; desired type = NSData; given type = Swift.__EmptyArrayStorage; value = (\n)."

This happens when calling try sharedModelContainer.mainContext.save() after inserting a CachedFood entity with an empty categories array.

Code Overview

I have a CachedFood model with a transformable categories property:

@Model
final class CachedFood {
    @Attribute(.unique) var id: String
    @Attribute(.transformable(by: "CategoriesTransformer"))
    var categories: [String]
    
    init(from food: Food) {
        self.id = food.id
        self.categories = food.category.isEmpty ? [] : food.category
    }
}

The transformer is defined as:

@objc(CategoriesTransformer)
final class CategoriesTransformer: ValueTransformer {
    override class func transformedValueClass() -> AnyClass {
        return NSData.self
    }
    
    override class func allowsReverseTransformation() -> Bool {
        return true
    }
    
    override func transformedValue(_ value: Any?) -> Any? {
        let array: [String]
        if let value = value as? [String] {
            array = value
        } else {
            array = []
        }
        return try? JSONEncoder().encode(array)
    }
    
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else {
            return [String]()
        }
        return (try? JSONDecoder().decode([String].self, from: data)) ?? [String]()
    }
}

The Problem

When I create a CachedFood with an empty categories array (like in the Food.dummy case), I get the error about unacceptable type. The error suggests SwiftData is getting a Swift.__EmptyArrayStorage type when it expects NSData. The transformer doesn't seem to be called for empty arrays, as I don't see the print statements from the transformer methods.

Why is the transformer not being called for empty arrays, and how can I properly handle transformable attributes that might be empty arrays in SwiftData?

Sample code to run:

// MARK: - App
@main
struct TestDataPlayApp: App {
    var sharedModelContainer: ModelContainer = {
        // Verify transformer is registered first
        print("Registering transformers...")
        registerGlobalTransformers()

        let schema = Schema([
            CachedFood.self,
        ])

        // Verify schema
        print("Creating schema with entities:", schema.entities.map { $0.name })

        let modelConfiguration = ModelConfiguration(
            schema: schema,
            isStoredInMemoryOnly: false,
            allowsSave: true
        )

        do {
            let container = try ModelContainer(
                for: schema,
                configurations: [modelConfiguration]
            )

            // Verify container creation
            print("Model container created successfully")
            print("Container schema:", container.schema)

            return container
        } catch {
            fatalError("Failed to create model container: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {

                    let cachedFood = CachedFood(from: .dummy)

                    sharedModelContainer.mainContext.insert(cachedFood)
                    do {
                        try sharedModelContainer.mainContext.save()
                    } catch {
                        print("Failed to cache food: \(error)")
                    }
                }
        }
        .modelContainer(sharedModelContainer)
    }
}

func registerGlobalTransformers() {
    // Register each transformer with its unique name
    ValueTransformer.setValueTransformer(
        CategoriesTransformer(),
        forName: NSValueTransformerName("CategoriesTransformer")
    )
}

// MARK: - Food App domain entity

struct Food: Identifiable, Codable {
    var id: String
    var category: [String]
}

// MARK: - Dummy food data for testing
extension Food {
    static let dummy = Food(id: "123", category: [])
}

// MARK: - Empty View
struct ContentView: View {
    var body: some View {
        EmptyView()
    }
}
1
  • 1
    See this answer for an in my opinion a much easier solution than using a transformer Commented May 10 at 11:48

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.