2

I am trying to migrate our Realm Swift schema from using a String wrapper class to the primitive String for collections. I am running into issues extracting the string values from the wrapper during migration.

Here is the wrapper class:

class StringDB: Object {
    @objc var stringVal: String = ""

    convenience init(string: String) {
        self.init()
        stringVal = string
    }
}

Then I have an example class with a List<StringDB> property:

class MyObjDB: Object {
    var emails: List<StringDB> = List<StringDB>()
    @objc dynamic var id: String = UUID().uuidString

    convenience init(_ emails: [StringDB]) {
        self.init()
        for email in emails {
            self.emails.append(objectsIn: emails)
        }
    }

    override static func primaryKey() -> String? {
        return "id"
    }
}

that I want to convert to a List<String>. Here is my migration code.

let config = Realm.Configuration(schemaVersion: latestSchemaVersion, migrationBlock: {
    migration, version in
    if version < 2 {
        migration.enumerateObjects(ofType: MyObjDB.className(), { old, new in
            guard let old = old, let new = new else { return }
            let oldEmails = old["emails"] as! List<StringDB>
            let newEmails = List<String>()
            
            for email in oldEmails {
                newEmails.append(email.stringVal)
            }
            new["emails"] = newEmails
        })
    }
})

However, the let oldEmails = old["emails"] as! List<StringDB> cast fails. I've tried also casting the collection to List<MigrationObject> and then casting the individual objects to StringDB but that cast fails as well.

I've found a workaround that may be satisfactory (I haven't confirmed yet), of converting the MigrationObject directly to string using coercion like "\(email)" and then running a regEx that will extract a desired substring from the garbage (StringDB {\n\tstringVal = [email protected];\n}), but I have no idea yet whether that will hold up in production, and I would prefer to work with something resembling a recommended way for doing this migration.

1 Answer 1

1

The Realm version is not shown in the question and there are a few typo's in the code. For older Realm versions, Lists should be defined thusly:

let emails = RealmSwift.List<StringDB>() //need RealmSwift. to differentiate it from Swift List

then newer versions should be this:

@Persisted var emails = RealmSwift.List<StringDB>()

Then this is a problem as it iterates over the emails array count times and appends the entire emails list over and over for every email in that list.

for email in emails {
   self.emails.append(objectsIn: emails)
}

Also, when using older Realm versons with @Objc property types, they need to include dynamic. So on the StringDB object this

@objc var stringVal: String = ""

should be this

@objc dynamic var stringVal: String = ""

Lastly, the MyObjDB needs to have somewhere to put the new email list. You can't overwrite the old one as it's not the correct type. So add a property

class MyObjDB: Object {
    var emails: List<StringDB> = List<StringDB>()
    let updatedEmailList = List<String>()

Then to the question: See comments in code for the flow

How about this:

migration.enumerateObjects(ofType: MyObjDB.className()) { oldItem, newItem in
    //instantiate a List of the old email objects as migration objects
    let oldEmailList = oldItem!["emails"] as! List<MigrationObject>
    
    //we're going to populate a new List with the email strings
    var newEmailList = List<String>()
    
    //iterate over the old list, extracting the string from the old object as
    //  a string an inject it into a new List of Strings
    for oldEmailObject in oldEmailList {
        let oldEmail = oldEmailObject["stringVal"] as! String
        newEmailList.append(oldEmail)
    }

    //assign the new List of strings to a new emails property
    newItem!["updatedEmailList"] = newEmailList
 }
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you. Apologies for the code errors, I was trying to make a toy example to not expose our code here, and I didn't put enough effort into making sure it would compile. As an aside, a teammate ended up finding .value(forKey: "stringVal") which appears to be equivalent to the subscript operator.

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.