Mapping JSON objects in custom objects

AppsDev picture AppsDev · Jul 29, 2013 · Viewed 8.4k times · Source

I've been searching if it is possible to get a JSON dictionary or array and directly map it in a custom object whose properties have the same name as the JSON tags, but I din't find any information regarding that.

I've been parsing JSON dictionaries manually, like this:

id deserializedObj = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData
                                                options:NSJSONReadingAllowFragments
                                                  error:&error];
if ([jsonObject isKindOfClass:[NSDictionary class]]) {
        NSDictionary *jsonDictionary = (NSDictionary *)jsonObject;

  if ([jsonDictionary objectForKey:idTag] != [NSNull null])
     [myObject setID:[[jsonDictionary objectForKey:@"id"] integerValue]];

  // Rest of properties
}

But I find weird having to parse each dictionary entry manually and not having a way to directly serialize it into a custom object, isn't there any other and faster way?

Note: I need my app to be compatible with iOS 5+

Thanks in advance

Answer

vilanovi picture vilanovi · Mar 18, 2014

I would propose you a library called Motis. It is a category on NSObject that does object mapping via KeyValueCoding. The nice thing of this category is that is very light-weight and performs automatic value validation trying to fit the class type of your properties (via introspection).

Most of all, what you have to do is define mappings (dictionaries) in your NSObject subclasses with pairs of "JSONKey": "PropertyName".

Also, if you have arrays, you can define mappings from your array names to the class types of its content enabling automatic object mapping for array content.

For example, If we try to map to our custom objects the following JSON:

{"video_id": 23,
 "video_title": "My holidays in Paris",
 "video_uploader":{"user_id":55,
                   "user_username": "johndoe"
                  },
 "video_people":[{"user_id":55,
                   "user_username": "johndoe"
                 },
                 {"user_id":45,
                   "user_username": "jimmy"
                 },
                 {"user_id":55,
                   "user_username": "martha"
                 }
                ]
 }

we would implement in our NSObject subclasses:

// --- Video Object --- //
@interface Video : NSObject
@property (nonatomic, assing) NSInteger videoId; 
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) User *uploader;
@property (nonatomic, strong) NSArray *peopleInVideo; // <-- Array of users
@end

@implementation Video
+ (NSDictionary*)mts_mapping
{
    return @{@"video_id" : mts_key(videoId),
             @"video_title" : mts_key(title),
             @"video_uploader" : mtsk_key(uploader),
             @"video_people": mts_key(peopleInVideo),
            };
}

+ (NSDictionary*)mts_arrayClassMapping
{
    return @{mts_key(peopleInVideo): User.class};
}

@end

// --- User Object --- //
@interface User : NSObject
@property (nonatomic, assing) NSInteger userId; 
@property (nonatomic, strong) NSString *username;
@end

@implementation User
+ (NSDictionary*)mts_mapping
{
    return @{@"user_id" : mts_key(userId),
             @"user_username" : mts_key(username),
            };
}

@end

And to perform the parsing and object mapping:

NSData *data = ... // <-- The JSON data
NSError *error = nil;
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];

if (error)
    return; // if error, abort.

Video *video = [[Video alloc] init];
[video mts_setValuesForKeysInDictionary:jsonObject]; // <-- Motis Object Mapping 

NSLog(@"Video: %@", video.description);

That simple. Here you can read more about it: http://github.com/mobilejazz/Motis

For more information check the blog post on benefits of using KeyValueCoding and distributed object mapping I've wrote in MobileJazz blog.

Hoping to be helpful.