Object Mapping library from JSON to NSObjects
I am trying to build a parser/objectMapper that will build Objective C objects for the JSON I consume from a REST service.
I took some inspiration from RestKit by having my Entities all hold a "decoding list" which tells a mapper which JSON keys goes with which objects. Like this:
//ObjectEntity implementation
+ (NSDictionary*) mapProperties {
/*
localPropertiy - JSONProperty
*/
return @{
@"name": @"name",
@"category": @"category",
@"possible_scopes": @"possibleScopes",
@"possible_descriptions": @"possibleDescriptions",
@"key": @"keys"
};
}
+ (NSDictionary*) mapRelations {
return [NSDictionary dictionary];
}
I did so because I like the encapsulation of these changeable values to be in the object that they reference. Making the Mapper know as little as possible.
The mapper does something like this:
+ (NSArray*) parseData:(NSData*) jsonData intoObjectsOfType:(Class) objectClass {
//Parser result from web service
NSError *error = nil;
CJSONDeserializer *deserializer = [CJSONDeserializer deserializer];
[deserializer setNullObject:nil];
NSArray *objects = [deserializer deserializeAsArray:jsonData error:&error];
NSMutableArray *result = [NSMutableArray array];
for (NSDictionary *o in objects) {
id <EntityProtocol> entity = [[objectClass alloc] init];
NSDictionary *jsonKeys = objectClass.mapProperties;
for (NSString *key in jsonKeys.allKeys) {
NSString *objectProperty = jsonKeys[key];
NSString *value = o[key];
if (value)
[entity setValue:value forKey:objectProperty];
}
[result addObject:entity];
}
return (NSArray*)result;
}
So I message the parser/mapper like this:
NSArray *objects = [ObjectParser parseData:self.responseData intoObjectsOfType:ObjectEntity.class];
This means that the parser must know what my root object is, which is fine as the o开发者_开发技巧bject retrieving it from the web service of course has this knowledge.
The above only works for JSON without nested objects, I have been trying to build the parser so that it will take the relationships into account as well, building the needed objects and inserting them into the root object, this needs to be recursive and I keep running into dead ends.
I would like some help to how I could approach this or any insight to as if something like this exists out as a library. Maybe for using or maybe just for tackling the parts I have problems with.
Thank you in advance.
Consider using RestKit: http://restkit.org
This framework has all you need — REST and JSON abstractions, object mapping, even Core Data support and a lot of really useful stuff, all implemented in a customizable and elegant manner.
UPDATE: Ok, while writing yet another mapping method, I decided I can't do it anymore and done a small framework. It introspects object's properties, and with a little tuning gives you free pretty description, isEqual/hashCode, free NSCoding support, and allows generating to/from JSON mappers (ok, actually, NSDictionary, but who would use it for something else). All NSNull-checks, missing fields in JSON, new unexpected fields in JSON are handled gracefully and reported properly.
If anybody wants this shared to the public, you could give me some upvotes or comments. I'd do that eventually, but I might consider sharing it faster.
Why not add mappings for classes?
+ (NSDictionary *)mapClasses {
return @{
@"category": [Category class],
// ...
};
}
For non-container properties, you could even do runtime introspection of properties to avoid redundant mappings.
Container properties could map to special wrapper objects:
[OMContainer arrayWithClass:Key.class], @"keys",
[OMContainer dicionaryWithKeyClass:ScopeID.class valueClass:Scope.class], @"possibleScopes",
Or even blocks for dynamic selection of types:
[OMDynamicType typeWithBlock:^(id obj){
if ([obj isKindOfClass:NSString.class] && [obj hasPrefix:@"foo"])
return Foo.class;
else
return Bar.class;
}], @"foo",
Implementing this could go something like:
+ (NSArray *)parseData:(NSData*)jsonData intoObjectsOfType:(Class)objectClass {
NSArray *parsed = /* ... */
NSMutableArray *decoded = [NSMutableArray array];
for (id obj in parsed) {
[decoded addObject:[self decodeRawObject:parsed intoObjectOfType:objectClass]];
}
return decoded;
}
+ (id)decodeRawObject:(NSDictionary *)dict intoObjectOfType:(Class)objectClass {
// ...
NSDictionary *jsonKeys = objectClass.mapProperties;
NSDictionary *jsonClasses = objectClass.mapClasses;
for (NSString *key in jsonKeys.allKeys) {
NSString *objectProperty = jsonKeys[key];
NSString *value = dict[key];
if (value) {
id klass = jsonClasses[key];
if (!klass) {
[entity setValue:value forKey:objectProperty];
} else if (klass == klass.class) {
[entity setValue:[self decodeRawObject:value intoObjectOfType:klass]
forKey:objectProperty];
} else if (/* check for containers and blocks */) {
// ...
}
}
}
// ...
}
I tried the same approach also. My main problem was, to describe the types accurately. For example for nested arrays I used something like this:
@{ @"friends" : @[[Person class]] }
But things became messy, because I needed to invent more and more special syntax for different types, that I wanted to support. In the end I stopped pursuing this approach and went for another solution - also because the runtime checks I needed to do were slowing the transformation down.
There are a lot of solutions out there right now. I would suggest to have a look at Awesome iOS website. And I also would like to point out JSON Class Generator, because it allows you to
- describe your types accurately (special mappers for date etc. are included),
- the types are automatically checked (mismatches reported and fallback values provided),
- you don't need to spend time for writing repetitive code (and the generated code seems to always "just work")
I hate to advertise, but this tool saved me a lot of work, that I invested into other features of my apps. It came out a little late, now that the world seems to switch to Swift, but even more reason to not waste time on writing models in Objective-C.
Here are some code examples converting to/form JSON:
// converting MyClass <-> NSDictionary
MyClass *myClass = [MyClass myClassWithDict:someJsonDictionary];
NSDictionary *jsonDict = [myClass toDict];
// converting NSDictionary <-> NSData
NSDictionary *someJsonDictionary = [NSDictionary api_dictionaryWithJson:someJsonData];
NSData *jsonData = [someJsonDictionary api_toJson];
The features mentioned in a previous comment are also supported by JSON Class Generator:
-isEqual, -hash, -description, -copy, NSCoding/NSSecureCoding/NSKeyedArchiver
support for free and does check for missing/additional/NSNull/optional fields and wrong types in the JSON response, reports errors, gracefully fails with fallback values and does this using method dispatch rather than runtime type introspection (which is faster).
Try out CSMapper, it is amazing! You just create plists named the same as the class, map some properties, then you can map a dictionary to your objects with ease with a single line of code. I have tested it on many projects and found it to be very clean, performant. It gave me flexibility to respond to API changes during the development lifecycle with ease.
https://github.com/marcammann/CSMapper
I am currently updating the documentation and adding on to the project on a personal fork which should hopefully be merged soon :)
https://github.com/AntonTheDev/CSMapper
My recommendation is to use the Motis category on NSObject. It is light-weight, performs automatic validation to your object types and very simple to use.
There is an exapmle in this other question: Mapping JSON objects in custom objects
Hope it is useful.
精彩评论