TestsTested | ✓ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Nov 2017 |
Maintained by Guille Gonzalez.
Groot provides a simple way of serializing Core Data object graphs from or into JSON.
It uses annotations in the Core Data model to perform the serialization and provides the following features:
NSValueTransformer
objects.Consider the following JSON describing a well-known comic book character:
{
"id": "1699",
"name": "Batman",
"real_name": "Bruce Wayne",
"powers": [
{
"id": "4",
"name": "Agility"
},
{
"id": "9",
"name": "Insanely Rich"
}
],
"publisher": {
"id": "10",
"name": "DC Comics"
}
}
We could translate this into a Core Data model using three entities: Character
, Power
and Publisher
.
Groot relies on the presence of certain key-value pairs in the user info dictionary associated with entities, attributes and relationships to serialize managed objects from or into JSON. These key-value pairs are often referred in the documentation as annotations.
In our example, we should add a JSONKeyPath
in the user info dictionary of each attribute and relationship specifying the corresponding key path in the JSON:
id
for the identifier
attribute,name
for the name
attribute,real_name
for the realName
attribute,powers
for the powers
relationship,publisher
for the publisher
relationship,Attributes and relationships that don't have a JSONKeyPath
entry are not considered for JSON serialization or deserialization.
When we created the model we decided to use Integer 64
for our identifier
attributes. The problem is that, for compatibility reasons, the JSON uses strings for id
values.
We can add a JSONTransformerName
entry to each identifier
attribute's user info dictionary specifying the name of a value transformer that converts strings to numbers.
Groot provides a simple way for creating and registering named value transformers:
// Swift
func toString(_ value: Int) -> String? {
return String(value)
}
func toInt(_ value: String) -> Int? {
return Int(value)
}
ValueTransformer.setValueTransformer(withName: "StringToInteger", transform: toString, reverseTransform: toInt)
// Objective-C
[NSValueTransformer grt_setValueTransformerWithName:@"StringToInteger" transformBlock:^id(NSString *value) {
return @([value integerValue]);
} reverseTransformBlock:^id(NSNumber *value) {
return [value stringValue];
}];
To preserve the object graph and avoid duplicating information when serializing managed objects from JSON, Groot needs to know how to uniquely identify your model objects.
In our example, we should add an identityAttributes
entry to the Character
, Power
and Publisher
entities user dictionaries with the value identifier
.
For more information about annotating your model have a look at Annotations.
Now that we have our Core Data model ready we can start adding some data.
// Swift
let batmanJSON: JSONDictionary = [
"name": "Batman",
"id": "1699",
"powers": [
[
"id": "4",
"name": "Agility"
],
[
"id": "9",
"name": "Insanely Rich"
]
],
"publisher": [
"id": "10",
"name": "DC Comics"
]
]
do {
let batman: Character = try object(fromJSONDictionary: batmanJSON, inContext: context)
} catch let error as NSError {
// handle error
}
// Objective-C
Character *batman = [GRTJSONSerialization objectWithEntityName:@"Character"
fromJSONDictionary:batmanJSON
inContext:self.context
error:&error];
If we want to update the object we just created, Groot can merge the changes for us:
// Swift
let updateJSON: JSONDictionary = [
"id": "1699",
"real_name": "Bruce Wayne",
]
do {
// This will return the previously created managed object
let batman: Character = try object(fromJSONDictionary: updateJSON, inContext: context)
} catch let error as NSError {
// handle error
}
Suppose that our API does not return full objects for the relationships but only the identifiers.
We don't need to change our model to support this situation:
// Swift
let batmanJSON: JSONDictionary = [
"name": "Batman",
"real_name": "Bruce Wayne",
"id": "1699",
"powers": ["4", "9"],
"publisher": "10"
]
do {
let batman: Character = try object(fromJSONDictionary: batmanJSON, inContext: context)
} catch let error as NSError {
// handle error
}
The above code creates a full Character
object and the corresponding relationships pointing to Power
and Publisher
objects that just have the identifier attribute populated.
We can import powers and publisher from different JSON objects and Groot will merge them nicely:
// Swift
let powersJSON: JSONArray = [
[
"id": "4",
"name": "Agility"
],
[
"id": "9",
"name": "Insanely Rich"
]
]
let publisherJSON: JSONDictionary = [
"id": "10",
"name": "DC Comics"
]
do {
let _: [Power] = try objects(fromJSONArray: powersJSON, inContext: context)
let _: Publisher = try object(fromJSONDictionary: publisherJSON, inContext: context)
} catch let error as NSError {
// handle error
}
Note that serializing relationships from identifiers only works with entities specifying only one attribute as the value of identityAttributes
annotation.
For more serialization alternatives check Groot.swift and GRTJSONSerialization.h.
Groot supports entity inheritance via the entityMapperName annotation.
If you are using SQLite as your persistent store, Core Data implements entity inheritance by creating one table for the parent entity and all child entities, with a superset of all their attributes. This can obviously have unintended performance consequences if you have a lot of data in the entities, so use this feature wisely.
Groot provides methods to serialize managed objects back to JSON:
// Swift
let result = json(fromObject: batman)
// Objective-C
NSDictionary *JSONDictionary = [GRTJSONSerialization JSONDictionaryFromObject:batman];
For more serialization alternatives check Groot.swift and GRTJSONSerialization.h.
Guillermo Gonzalez
@gonzalezreal
Groot is available under the MIT license.