Andrew
Andrew

Reputation: 1289

How to properly map JSON properties to model properties in Realm.create

I'm currently implementing an insert-or-update pattern with Realm by calling realm.create(T.self, value: object, update: true). object is JSON returned from a rest call and it may contain some or all of the properties associated with that realm object. (In other words, I need to support partial updates.)

In many of the JSON objects there is a key called description. Since I cannot have a Realm object subclass with a property called public dynamic var description I need to choose another name and make sure it is mapped properly when calling realm.create. I know what you are thinking, I can just do the mapping before calling create. However, the JSON may also have nested objects / array of objects and it seems redundant for me to setup mappings for all nested properties as well, when Realm already knows where those objects map to. It would be much cleaner if I could simply define a mapping in each Object subclass and then Realm could figure out the rest.

My first attempt at solving this was to override the init functions for Object (even though comments explicitly say not to, had to try) and that did not work because none of the init methods that take in a JSON object were actually called with using realm.create.

Is there any way to make my life easier here?

Note: I am using Swift 2.0 and the swift-2.0 branch of Realm

Upvotes: 2

Views: 4595

Answers (3)

Noobass
Noobass

Reputation: 2014

You can use also SwiftyJSONModel. The realm Model will look something like this:

final class Person: Object {
    dynamic var name = ""
    dynamic var age = ""
    dynamic var isMarried = false
}

extension Person: JSONObjectInitializable {
    enum PropertyKey: String {
        case name, age, isMarried
    }

    convenience init(object: JSONObject<PropertyKey>) throws {
        self.init()
        name = try object.value(for: .name)
        age = try object.value(for: .age)
        isMarried = try object.value(for: .isMarried)
    }
}

This framework has some nice features, like:

  1. Keys are all incapsulated in enum and as a result you receive autocompletion
  2. All types are inferred and there is no need to specify if it's a String or Int
  3. You will receive a verbose error if the JSON was invalid. This really saves a lot of time
  4. Easy accessing nested JSON

Upvotes: 1

Alexis C.
Alexis C.

Reputation: 4918

Realm still doesn't support proper mapping when using Swift, so let me elaborate on jpsim comment.

ObjectMapper provides a powerful mapping mechanism which not only let you choose your model properties names, but also handle nicely any sanitization you might need during the mapping process.

Here is an example I've written for a dummy project :

import ObjectMapper

class Color: RLMObject, Mappable{

    dynamic var myId = 0
    dynamic var myTitle = ""
    dynamic var username = ""
    dynamic var hex = ""

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

    required convenience init?(_ map: Map){
        self.init()
    }

    static func mappedColor(dict:Dictionary<String, AnyObject>) -> Color{
        return Mapper<Color>().map(dict)! as Color
    }

    func mapping(map: Map) {
        id <- map["id"]
        title <- map["title"]
        username <- map["userName"]
        hex <- map["hex"]
    }
}

You can see I have both a mapping function linked to the ObjectMapper Mappable protocol, as well as a mappedColor function which is nothing more than a convenient way to map a JSON to retrieve my model object.

I can then use it within a webservice call response likeso :

[...]
if let JSON:Array = response.result.value as? Array<[String: AnyObject]> {

    do{
        try RLMRealm.defaultRealm().transactionWithBlock {
            for dict in JSON{
                let color = Color.mappedColor(dict)
                RLMRealm.defaultRealm().addOrUpdateObject(color)
            }
        }
    } catch let error as NSError {
        print(error)
    }
}
[...]

Upvotes: 3

jpsim
jpsim

Reputation: 14409

Realm doesn't have any built-in keypath/value mapping mechanisms, so whatever you pass to Object(value:) needs to match that model's schema exactly. Just like KVC.

Whatever you do, you'll need to sanitize (e.g. map keys, transform values, fold/unfold objects) before passing those values into Realm. You can either do this yourself by recursively walking your dictionary/array obtained from JSON, or by using a library like ObjectMapper, which aims to simplify this kind of work. Here's one ObjectMapper & Realm user's way of using both tools together.

It's worth noting that Realm does intend to eventually support this kind of key mapping in the future (tracked as #694), but work on that hasn't yet begun.

Upvotes: 0

Related Questions