swiftrealmrealm-migrationrealm-embedded-object

Realm Swift Error: Embedded objects cannot be created directly


I am trying to migrate an object with a property of type List<String> to type List<ChildObject> where ChildObject is a custom EmbeddedObject.

Example

Here's what I mean:

import RealmSwift

final class ParentObject: Object {
    // Previously, this property was of type `List<String>`.
    @Persisted public var children: List<ChildObject>
}

final class ChildObject: EmbeddedObject {
    @Persisted var name = ""
}

I'm using this code to perform the migration, which is producing the error:

Embedded objects cannot be created directly

let configuration = Realm.Configuration(schemaVersion: 1) { migration, oldSchemaVersion in
    if oldSchemaVersion < 1 {
        migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
            let childrenStrings = oldObject!["children"] as! List<DynamicObject>
            let childrenObjects = newObject!["children"] as! List<MigrationObject>

            // I'm trying to retain the previous values for `children` (of type `String`) 
            // where each value is used as the `name` property of a new `ChildObject`.
            for string in childrenStrings {
                childrenObjects.append(
                    // This line produces the error :(
                    migration.create(ChildObject.className(), value: [string])
                )
            }
        }
    }
}

let realm = try! Realm(configuration: configuration)

Question

How do I perform the migration while retaining the previous values?


Solution

  • The easiest thing to do is to create a Dictionary with all of the property name/value pairs of each child object and create the new List in its entirety with an Array of those pairs.

    But first you need to extract the String values from the old List of children. The reason this is necessary is because Realm does not represent the element of List<String> as an actual String (which is a struct). We can extract the actual value from the description field:

    childrenStrings
        .map { String(describing: $0) }
    

    Once you have the name you can represent the new ChildObject with a Dictionary. Note that you will have to include all property names and values in the Dictionary. Since we only have one, called "name", we include that:

    childrenStrings
        .map { String(describing: $0) }
        .map { ["name": $0] }
    

    Your error message said:

    Embedded objects cannot be created directly

    However, you can create your new objects using an Array of Dictionary where the Array corresponds to the List and each Dictionary corresponds to a ChildObject object. Putting it all together we have:

    migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
        let childrenStrings = oldObject!["children"] as! List<DynamicObject>
    
        newObject?.setValue(
            Array(
                childrenStrings
                    .map { String(describing: $0) }
                    .map { ["name": $0] }
            ),
            forKey: "children"
        )
    }