SyncDB/SYNCPropertyMapper

Map your Core Data properties with ease


License
GPL-3.0+

Documentation

SYNCPropertyMapper

SYNCPropertyMapper leverages on your Core Data model to infer how to map your JSON values into Core Data. It's simple and it's obvious. Why the hell isn't everybody doing this?

Table of Contents

Filling a NSManagedObject with JSON

Mapping your Core Data objects with your JSON providing backend has never been this easy.

JSON in CamelCase

{
  "firstName": "John",
  "lastName": "Hyperseed"
}
NSDictionary *values = [JSON valueForKey:@"user"];
[user hyp_fillWithDictionary:values];

Your Core Data entities should match your backend models. Your attributes should match their JSON counterparts. For example firstName maps to firstName, address to address.

JSON in snake_case

{
  "first_name": "John",
  "last_name": "Hyperseed"
}
NSDictionary *values = [JSON valueForKey:@"user"];
[user hyp_fillWithDictionary:values];

Your Core Data entities should match your backend models but in camelCase. Your attributes should match their JSON counterparts. For example first_name maps to firstName, address to address.

Attribute Types

String and Numbers

This is pretty straightforward and should work as you would expect it. A JSON string maps to NSString and double, float, ints and so on, map to NSNumber.

Date

We went for supporting ISO 8601 and unix timestamp out of the box because those are the most common formats when parsing dates, also we have a quite performant way to parse this strings which overcomes the performance issues of using NSDateFormatter.

NSDictionary *values = @{@"created_at" : @"2014-01-01T00:00:00+00:00",
                         @"updated_at" : @"2014-01-02",
                         @"published_at": @"1441843200"
                         @"number_of_attendes": @20};

[managedObject hyp_fillWithDictionary:values];

NSDate *createdAt = [managedObject valueForKey:@"createdAt"];
// ==> "2014-01-01 00:00:00 +00:00"

NSDate *updatedAt = [managedObject valueForKey:@"updatedAt"];
// ==> "2014-01-02 00:00:00 +00:00"

NSDate *publishedAt = [managedObject valueForKey:@"publishedAt"];
// ==> "2015-09-10 00:00:00 +00:00"

If your date is not ISO 8601 compliant, you can use a transformer attribute to parse your date, too. First set your attribute to Transformable, and set the name of your transformer like, in this example is DateStringTransformer:

transformable-attribute

You can find an example of date transformer in DateStringTransformer.

Array

For mapping for arrays first set attributes as Binary Data on the Core Data modeler.

screen shot 2015-04-02 at 11 10 11 pm

let values = ["hobbies" : ["football", "soccer", "code"]]
managedObject.hyp_fill(with: values)
let hobbies = NSKeyedUnarchiver.unarchiveObject(with: managedObject.hobbies) as! [String]
// ==> "football", "soccer", "code"

Dictionary

For mapping for dictionaries first set attributes as Binary Data on the Core Data modeler.

screen shot 2015-04-02 at 11 10 11 pm

let values = ["expenses" : ["cake" : 12.50, "juice" : 0.50]]
managedObject.hyp_fill(with: values)
let expenses = NSKeyedUnarchiver.unarchiveObject(with: managedObject.expenses) as! [String: Any]
// ==> "cake" : 12.50, "juice" : 0.50

Exceptions

There are two exceptions to this rules:

  • ids should match remoteID
  • Reserved attributes should be prefixed with the entityName (type becomes userType, description becomes userDescription and so on). In the JSON they don't need to change, you can keep type and description for example. A full list of reserved attributes can be found here.

Custom

Remote mapping documentation

  • If you want to map your Core Data identifier (key) attribute with a JSON attribute that has different naming, you can do by adding hyper.remoteKey in the user info box with the value you want to map.

Deep mapping

{
  "id": 1,
  "name": "John Monad",
  "company": {
    "name": "IKEA"
  }
}

In this example, if you want to avoid creating a Core Data entity for the company, you could map straight to the company's name. By adding this to the User Info of your companyName field:

hyper.remoteKey = company.name

Dealing with bad APIs

Sometimes values in a REST API are not formatted in the way you want them, resulting in you having to extend your model classes with methods and/or properties for transformed values. You might even have to pre-process the JSON so you can use it with SYNCPropertyMapper, luckily most of this cases could be solved by using a ValueTransformer.

For example, in my user model instead of getting this:

{
  "name": "Bob Dylan"
}

Our backend developer decided he likes arrays, so we're getting this:

{
  "name": [
    "Bob Dylan"
  ]
}

Since SYNCPropertyMapper expects just a name with value Bob Dylan, we have to pre-process this value before getting it into Core Data. For this, first we'll create a subclass of ValueTransformer.

import Foundation

class BadAPIValueTransformer : ValueTransformer {
    override class func transformedValueClass() -> AnyClass {
        return String.self as! AnyClass
    }

    override class func allowsReverseTransformation() -> Bool {
        return true
    }

    // Used to transform before inserting into Core Data using `hyp_fill(with:)
    override func transformedValue(_ value: Any?) -> Any? {
        guard let valueToTransform = value as? Array<String> else {
            return value
        }

        return valueToTransform.first!
    }

    // Used to transform before exporting into JSON using `hyp_dictionary`
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let stringValue = value as? String else { return value }

        return [stringValue]
    }
}

Then we'll add another item in the user key of our Core Data attribute. The key will be hyper.valueTransformer and the value BadAPIValueTransformer.

value-transformer

Then before hyp_fill(with:) we'll do

ValueTransformer.setValueTransformer(BadAPIValueTransformer(), forName: NSValueTransformerName(rawValue: "BadAPIValueTransformer"))

That's it! Then your name will be Bob Dylan, congrats with the Peace Nobel Prize.

By the way, it works the other way as well! So using hyp_dictionary will return ["Bob Dylan"].

JSON representation from a NSManagedObject

UserManagedObject *user;
[user setValue:@"John" forKey:@"firstName"];
[user setValue:@"Hyperseed" forKey:@"lastName"];

NSDictionary *userValues = [user hyp_dictionary];

That's it, that's all you have to do, the keys will be magically transformed into a snake_case convention.

{
  "first_name": "John",
  "last_name": "Hyperseed"
}

Excluding

If you don't want to export attribute / relationship, you can prohibit exporting by adding hyper.nonExportable in the user info of the excluded attribute or relationship.

non-exportable

Relationships

It supports relationships too, and we complain to the Rails rule accepts_nested_attributes_for, for example for a user that has many notes:

"first_name": "John",
"last_name": "Hyperseed",
"notes_attributes": [
  {
    "0": {
      "id": 0,
      "text": "This is the text for the note A"
    },
    "1": {
      "id": 1,
      "text": "This is the text for the note B"
    }
  }
]

If you don't want to get nested relationships you can also ignore relationships:

let dictionary = user.hyp_dictionary(using: .none)
"first_name": "John",
"last_name": "Hyperseed"

Or get them as an array:

let dictionary = user.hyp_dictionary(using: .array)
"first_name": "John",
"last_name": "Hyperseed",
"notes": [
  {
    "id": 0,
    "text": "This is the text for the note A"
  },
  {
    "id": 1,
    "text": "This is the text for the note B"
  }
]

Installation

SYNCPropertyMapper is available through CocoaPods. To install it, simply add the following line to your Podfile:

use_frameworks!

pod 'SYNCPropertyMapper', '~> 5'

SYNCPropertyMapper is also available through Carthage. To install it, simply add the following line to your Cartfile:

github "SyncDB/SYNCPropertyMapper" ~> 5.0

Contributing

Please Hyper's playbook for guidelines on contributing.

Credits

Hyper made this. We're a digital communications agency with a passion for good code, and if you're using this library we probably want to hire you.

License

SYNCPropertyMapper is available under the MIT license. See the LICENSE file for more info.