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?
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"
)
}
To summarize the solutions from Rob and Jay respectively:
Option 1
This is Rob's solution with modifications.
migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
let childrenStrings = oldObject!["children"] as! List<DynamicObject>
newObject!.setValue(
Array(childrenStrings.map { [$0] }),
forKey: "children"
)
}
Option 2
This is a solution based on Jay's suggestions. It creates fewer arrays (or dictionaries if using named parameters) and should scale better with more complex objects.
migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
let childrenStrings = oldObject!["children"] as! List<DynamicObject>
newObject!["children"] = childrenStrings.map {
let childObject = ChildObject()
childObject.name = "\($0)"
return childObject
} as [ChildObject]
}
Related
We have a Realm object model that looks like this:
class RealmGroup: Object {
#Persisted var name: String?
#Persisted var groupId: Int? // this *should* have been the primary key
#Persisted var groupPath: String?
}
We also have multiple object models that define a "group" property of type RealmGroup. For example:
class A: Object {
// other properties removed for brevity
#Persisted var group: RealmGroup?
}
class B: Object {
#Persisted var group: RealmGroup?
}
class C: Object {
#Persisted var group: RealmGroup?
}
On one of my development phones, there are approximately 1000 A, B and C objects in the realm. And across all 1000 of those objects, there may only be 10 - 15 unique group IDs. The "groupId" property on the RealmGroup object should have been declared as the primary key so we'd only ever have one instance of RealmGroup with "groupId" = 12345, for example. And then, of course, every A, B and C object belonging to that group would reference the same RealmGroup object. But unfortunately, that wasn't caught until after we put this code into production. So now I'm looking into the possibility of "fixing" this retroactively through a migration.
It seems understandable that I can't simply make the "groupId" property the primary key on the existing RealmGroup model since we already have multiple instances of RealmGroup objects with the same "groupId" value in the realm. But beyond that, I'm not sure how I'd be able to implement this inside of a migration block. The Realm Swift SDK docs cover a few examples of migration but not exactly something like what I'm trying to accomplish.
Is it possible to do this without needing to create a new model (e.g., RealmGroupV2)?
I'm not sure if there's a better way to do this but I did finally get it working. First, I created a new RealmGroupV2 model with the group ID defined as the primary key. Then I added a new .group_v2 property to my A, B and C objects. Finally, I performed the migration like this:
// enumerate all of the existing RealmGroup objects and create one new RealmGroupV2
// object for each unique group ID
var groupIds: [Int] = [] // keep track of the group IDs we've processed
migration.enumerateObjects(ofType: RealmGroup.className(), { oldGroupObject, _ in
guard let oldGroupObject = oldGroupObject else { return }
guard let groupId = oldGroupObject.value(forKey: "groupId") as? Int else { return }
guard let name = oldGroupObject.value(forKey: "name") as? String else { return }
guard let groupPath = oldGroupObject.value(forKey: "groupPath") as? String else { return }
if groupIds.contains(groupId) == false {
// create a new RealmGroupV2 object for this group
let groupV2 = migration.create(RealmGroupV2.className())
groupV2["groupId"] = groupId
groupV2["name"] = name
groupV2["groupPath"] = groupPath
// add groupId to the groupIds array so we don't try to create another
// RealmGroupV2 for this groupId
groupIds.append(groupId)
}
})
// enumerate all of the existing A objects, assign a RealmGroupV2 object to the
// new .group_v2 property and set the now-deprecated .group property to nil
migration.enumerateObjects(ofType: A.className(), { oldObject, newObject in
guard let oldObject = oldObject else { return }
guard let newObject = newObject else { return }
guard let groupObject = oldObject["group"] as? Object else { return }
guard let groupId = groupObject.value(forKey: "groupId") as? Int else { return }
migration.enumerateObjects(ofType: RealmGroupV2.className(), { oldGroupV2Object, newGroupV2Object in
guard let newGroupV2Object = newGroupV2Object else { return }
if let groupIdFromV2Object = newGroupV2Object["groupId"] as? Int {
if groupIdFromV2Object == groupId {
newObject["group_v2"] = newGroupV2Object
newObject["group"] = nil
}
}
})
})
// *** repeat above migration for B and C objects ***
// finally, delete all of the old RealmGroup objects
migration.deleteData(forType: RealmGroup.className())
I'm sure there are ways to improve upon this but hopefully it will help someone else who might be struggling with a similar situation.
I have a core data framework to handle everything you can do with coredata to make it more cooperateable with codable protocol. Only thing i have left is to update the data. I store and fetch data by mirroring the models i send as a param in their functions. Hence i need the variable names in the models if i wish to only update 1 specific value in the model that i request.
public func updateObject(entityKey: Entities, primKey: String, newInformation: [String: Any]) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityKey.rawValue)
do {
request.predicate = NSPredicate.init(format: "\(entityKey.getPrimaryKey())==%#", primKey)
let fetchedResult = try delegate.context.fetch(request)
print(fetchedResult)
guard let results = fetchedResult as? [NSManagedObject],
results.count > 0 else {
return
}
let key = newInformation.keys.first!
results[0].setValue(newInformation[key],
forKey: key)
try delegate.context.save()
} catch let error {
print(error.localizedDescription)
}
}
As you can see the newInformation param contains the key and new value for the value that should be updated. However, i dont want to pass ("first": "newValue") i want to pass spots.first : "newValue"
So if i have a struct like this:
struct spots {
let first: String
let second: Int
}
How do i only get 1 name from this?
i've tried:
extension Int {
var name: String {
return String.init(describing: self)
let mirror = Mirror.init(reflecting: self)
return mirror.children.first!.label!
}
}
I wan to be able to say something similar to:
spots.first.name
But can't figure out how
Not sure that I understood question, but...what about this?
class Spots: NSObject {
#objc dynamic var first: String = ""
#objc dynamic var second: Int = 0
}
let object = Spots()
let dictionary: [String: Any] = [
#keyPath(Spots.first): "qwerty",
#keyPath(Spots.second): 123,
]
dictionary.forEach { key, value in
object.setValue(value, forKeyPath: key)
}
print(object.first)
print(object.second)
or you can try swift keypath:
struct Spots {
var first: String = ""
var second: Int = 0
}
var spots = Spots()
let second = \Spots.second
let first = \Spots.first
spots[keyPath: first] = "qwerty"
spots[keyPath: second] = 123
print(spots)
however there will be complex (or impossible) problem to solve if you will use dictionary:
let dictionary: [AnyKeyPath: Any] = [
first: "qwerty",
second: 123
]
you will need to cast AnyKeyPath back to WritableKeyPath<Root, Value> and this seems pretty complex (if possible at all).
for path in dictionary.keys {
print(type(of: path).rootType)
print(type(of: path).valueType)
if let writableKeyPath = path as? WritableKeyPath<Root, Value>, let value = value as? Value { //no idea how to cast this for all cases
spots[keyPath: writableKeyPath] = value
}
}
[I am new to Swift, I don't know is this possible or not, so please suggest me]
I have a dictionary (which is dynamic) like this:
let simpleHash = ["testA": "A", "testB": "B", "testC": "C"]
I want to convert this to an Object, so that I can access like:
simpleHash.testA // instead of simpleHash["testA"]
I have tried the below one, but it didn't help
let jsonData = try JSONSerialization.data(withJSONObject: simpleHash, options: .prettyPrinted)
let decoded = try JSONSerialization.jsonObject(with: jsonData, options: [])
Can anyone please suggest me on this.
Thanks in advance!
Swift will need an explicitly declared variable for testA so you will not be able to be 100% dynamic. But, since you need to use the variable in code, it will be known at some point. Given this and in the spirit of minimizing the declaration constraints, you could define a class that uses the dictionary as its internal storage and exposes the key values as computed properties.
here's an example:
class DictionaryBased
{
var content:[String:Any]
init(_ dictionary:[String:Any])
{ content = dictionary }
func get<T>(_ key:String, _ defaultValue:T) -> T
{ return content[key] as? T ?? defaultValue }
func set<T>(_ key:String, _ value:T)
{ content[key] = value }
}
class SimpleHash:DictionaryBased
{}
With this, you can add computed properties as needed (and where needed) using extensions.
extension SimpleHash
{
var testA:String { get { return get("testA", "") } set { set("testA",newValue) } }
var testB:String { get { return get("testB", "") } set { set("testB",newValue) } }
// if variables are "read-only", you don't need the set { } part
var testC:String { get { return get("testC", "") } }
}
You can add variables that are typed or not and support optionals or, (as above) provide default values.
extension SimpleHash
{
var testD:Any? { get { return get("testD", nil) } set { set("testD",newValue) } }
var testE:String? { get { return get("testE", nil) } set { set("testE",newValue) } }
var testF:Date? { get { return get("testF", nil) } set { set("testE",newValue) } }
}
To use this "dictionary based" object, you would need to create an instance at some point and give it the dictionary's content:
let simpleHash = SimpleHash(["testA": "A", "testB": "B", "testC": "C"])
simpleHash.testA // "A"
simpleHash.testD // nil
Note that, this isn't going to be as efficient as using native properties and mapping the dictionary to each physical variable. On the other hand, it is a lot less code so. If the variables are not referenced often, the extra overhead may be an acceptable trade off for simplicity and flexibility.
A simple struct to hold your Dictionary values:
struct SimpleStruct {
// properties are Optional since they might not be matched
let testA: String?
let testB: String?
// this one has a default value
let testC: String
// init that takes a Dictionary
init(dictionary: [String:Any]) {
// set the Optional ones
self.testA = dictionary["testA"] as? String
self.testB = dictionary["testB"] as? String
// set the one with a default
self.testC = dictionary["testC"] as? String ?? "C"
}
}
let foo = SimpleStruct(dictionary: ["testA": "A", "testB": "B", "testC": "C"])
// force-unwrapping for brevity
// you should actually test before using
print(foo.testA!) // prints A
print(foo.testB!) // prints B
print(foo.testC) // prints C
I am currently learning Realm and am converting my experimental app/game which uses arrays to Realm;
It loads pre-seeding data via a local JSON file and ObjectMapper; then creates objects in realm; this part seems to work.
// Parse response
let json = try! JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! Array<Dictionary<String, AnyObject>>
let factories = Mapper<Factory>().mapArray(JSONArray: json)!
do {
let realm = try Realm()
try realm.write {
for factory in factories
{
realm.add(factory, update: true)
}
}
} catch let error as NSError {
print(error.localizedDescription as Any)
}
The issue I'm having is that when it maps; I'd like it to create its child objects at the same time and link them to parent.
Each parent (Factory) has about between 4 children (Engine) linked to it.
// Factory is parent object
class Factory: Object, Mappable {
dynamic var name: String = ""
let engines = List<Engine>()
//Impl. of Mappable protocol
required convenience init?(map: Map) {
self.init()
}
// Mappable
func mapping(map: Map) {
name <- map["name"]
}
}
// Engine is a child to Factory
class Engine: Object {
dynamic var production: Int = 0
// create children and add to the parent factory
static func createEngines(parent:Factory) -> [Engines]
{
var engines:[Engine] = [Engine]()
for _ in stride(from:0, to: 3, by: 1) {
//let engine : Engine = Engine.init(parent: element)
//engines.append(engine)
}
return engines
}
}
If I attempt to put this in my mappable
engines = Engine.createEngines(parent: self)
and make a change in my Factory model;
`var engines = List<Engine>()`
I get this error:
Cannot assign value of type '[Engine]?' to type 'List<Engine>'
The problem here is that simply creating an array of engines (children), appending it to an array doesn't seem to work with Realm and I'm not sure how to do this.
Hence, my question is how do I bulk create children, assign it to a given parent and add it to the current realm write/save?
Many thanks.
I changed my code to do this;
Read all the factories from JSON
Loop through the factories, creating engines
Link the parent object up.
I'm not sure if I did it right but it seems to be working.
I just don't like how I'm having to hardwire the parent; as I thought Realm/ObjectMapper could do that for me. But its not a major issue as there is only about 3 or 4 relationships.
let factories = Mapper<Factory>().mapArray(JSONArray: json)!
do {
let realm = try Realm()
try realm.write {
for f in factories
{
realm.add(f, update: true)
}
let factories = realm.objects(Factory.self)
print (factories.count) // for debug purposes
for f in factories {
for _ in stride(from: 0, to: f.qty, by: 1) {
let engine : Engine = Engine.init()
engine.parent = f
f.engines.append(engine)
}
}
}
} catch let error as NSError {
print(error.localizedDescription as Any)
}
This above code seems to do the work for me; although I do wish I didn't have to manually set the parent (engine.parent = f)
Anyhow, I've accepted #BogdanFarca's answer.
There is a very nice solution by Jerrot here on Githib Gist
The mapping should be defined in your main model object like this:
func mapping(map: Map) {
title <- map["title"]
products <- (map["products"], ArrayTransform<ProductModel>())
}
The real magic is happening in the ArrayTransform class:
func transformFromJSON(value: AnyObject?) -> List<T>? {
var result = List<T>()
if let tempArr = value as! Array<AnyObject>? {
for entry in tempArr {
let mapper = Mapper<T>()
let model : T = mapper.map(entry)!
result.append(model)
}
}
return result
}
I have several swift classes that look similar like the following
public class Book {
var title: String?
var date: NSDate?
}
As there are several different classes where I need to access the properties, I am using reflection to run through the properties of the class:
let myBook = Book()
myBook.title = "Hello"
myBook.date = NSDate()
let mirror = Mirror(reflecting: myBook)
var propsArr = [(key: String?, value: Any)]()
let mirrorChildrenCollection = AnyRandomAccessCollection(mirror.children)!
if mirrorChildrenCollection.count > 0 {
propsArr += mirrorChildrenCollection
}
//iterate through properties
for case let (label?, value) in propsArr {
print (label, value)
if let val = value as? NSDate {
var extractedDate = val
print(extractedDate)
}
else if let val = value as? String {
var extractedTitle = val
print (extractedTitle)
}
}
But I have a problem that the Child objects are not extracted as they are of Type Any and internally optional classes and thus do not fall into my cases. If I change title from String? to String, they do work, but I need to use optional types.
What can I change in the above implementation to leave the datatype as String? and Date? and still extract the values from the Mirror?
It seems this isn't possible in Swift 2.x.
Since the properties are optionals, you would have to cast to NSDate? and String?, respectively, like this:
if let val = value as? NSDate? {
// val is Optional<NSDate>
}
Unfortunately, the compiler doesn't allow this (I’m not sure why): // error: cannot downcast from 'protocol<>' to a more optional type 'NSDate?'.
This answer from bubuxu provides a clever workaround that would work if you had a Mirror for each property. The mirror's displayStyle property tells you if it is an optional, and you can then extract the wrapped value manually. So this would work:
let child = Mirror(reflecting: myBook.date)
child.displayStyle
if child.displayStyle == .Optional {
if child.children.count == 0 {
// .None
} else {
// .Some
let (_, some) = child.children.first!
if let val = some as? NSDate {
print(val)
}
}
}
But this depends on having a Mirror for each property, and it seems you can’t traverse a Mirror's children to retrieve Mirrors for them.