TestsTested | ✓ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Nov 2016 |
SPMSupports SPM | ✓ |
Maintained by Elvis Nuñez.
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?
Mapping your Core Data objects with your JSON providing backend has never been this easy.
{
"firstName": "John",
"lastName": "Hyperseed"
}
NSDictionary *values = [JSON valueForKey:@"user"];
[user hyp_fillWithDictionary:values];
let userJSON = JSON["user"]
user.hyp_fill(with: userJSON)
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
.
{
"first_name": "John",
"last_name": "Hyperseed"
}
NSDictionary *values = [JSON valueForKey:@"user"];
[user hyp_fillWithDictionary:values];
let userJSON = JSON["user"]
user.hyp_fill(with: userJSON)
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
.
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.
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
:
You can find an example of date transformer in DateStringTransformer.
For mapping for arrays first set attributes as Binary Data
on the Core Data modeler.
let values = ["hobbies" : ["football", "soccer", "code"]]
managedObject.hyp_fill(with: values)
let hobbies = NSKeyedUnarchiver.unarchiveObject(with: managedObject.hobbies) as! [String]
// ==> "football", "soccer", "code"
For mapping for dictionaries first set attributes as Binary Data
on the Core Data modeler.
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
There are two exceptions to this rules:
id
s should match remoteID
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.hyper.remoteKey
in the user info box with the value you want to map.{
"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
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
.
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"]
.
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"
}
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.
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"
}
]
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
Please Hyper's playbook for guidelines on contributing.
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.
SYNCPropertyMapper is available under the MIT license. See the LICENSE file for more info.