Parsing JSON with Objective-C and RestKit

Parsing JSON with Objective-C and RestKit

Another problem I have found while developing my pet-project application called ‘BeaconHarvester‘ was to integrate it with the external web service somewhere to allow people exchange information about iBeacons found, this functionality is on its way but for now I would like to share a few bits on how to parse quite complex JSON data with very popular RestKit framework.

As I just basically started with Objective-C I still feel like working with JSON/REST and HTTP on iOS is a bad dream, if anyone reads this and knows a better solution to this problem I welcome you in my comments section 🙂

So, best so far I have found is the mentioned ‘RestKit‘ framework, seems pretty cool but the amount of mapping boilerplate code you need to write to make it work is just pure craziness. Anyway, lets get started, here is our JSON

{
    "rows": [
        {
            "bbox": [
                10,
                20,
                10,
                20
            ],
            "geometry": {
                "coordinates": [
                    10,
                    20
                ],
                "type": "Point"
            },
            "id": "x1",
            "value": {
                "name": "estimote"
            }
        }
    ],
    "update_seq": 31
}

 
for simplicity I have reduced it to be just a single object inside the array called ‘rows’. This is the standard output of the geospatial (bounding box) query I run against my geocouch database instance storing the data about iBeacons (location and name).
What we are interested in this response are ‘rows’ holding an array of iBeacon object, single iBeacon object with ‘geometry’ and ‘value’ nested objects. ‘geometry’ contains property called ‘coordinates’ with latitude and longitude as simple numbers. On top of that we want to retrieve the ‘value’ object which will contain all the other fields emited by geocouch for this document. In other words we are not interested in ‘bbox’ object and ‘update_seq’, the rest we want to parse into our Objective-C representation and use it later in our application.

First we define simple POJOS into which we will parse that JSON response, the root object will be called BBoxResponse:

#import <Foundation/Foundation.h>

@interface BBoxResponse : NSObject
@property (nonatomic, retain) NSArray *rows;
@end

it contains a single array for storing all retrieved objects.

second comes the object contained in the array itself, I call it ‘iBeacon’

#import <Foundation/Foundation.h>

@class IBeaconGeometry;
@class IBeaconValue;

@interface IBeacon : NSObject
@property (nonatomic, copy) NSString *beaconId;
@property (nonatomic) IBeaconValue *value;
@property (nonatomic) IBeaconGeometry *geometry;
@end

inside that object we have another nested objects for ‘value’ and ‘geometry’ properties:

#import <Foundation/Foundation.h>

@interface IBeaconValue : NSObject
@property (nonatomic, copy) NSString *name;
@end

//and geometry:

@interface IBeaconGeometry : NSObject
@property (nonatomic, copy) NSString *type;
@property (nonatomic, retain) NSArray *coordinates;
@end

‘coordinates’ are just simple decimals so there is no need to map them into a separate object.

The nice thing about RestKit is that it allows you to directly map the response data into your Objective-C pojos, not so nice is the amount of code you need to write to make it work, even if the name of the properties in your JSON data are the same as the properties in Objective-C classes, anyway here is the mapping:

    //added because default response type in CouchDB is text/plain
    [RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:@"text/plain"];

    //mappings
    RKObjectMapping *beaconGeometryMapping = [RKObjectMapping mappingForClass:[IBeaconGeometry class]];
    [beaconGeometryMapping addAttributeMappingsFromArray:@[@"type",@"coordinates"]];
   
    RKObjectMapping *beaconValueMapping = [RKObjectMapping mappingForClass:[IBeaconValue class]];
    [beaconValueMapping addAttributeMappingsFromArray:@[ @"name"]];
   
    RKObjectMapping *beaconsMapping = [RKObjectMapping mappingForClass:[IBeacon class]];
    [beaconsMapping addAttributeMappingsFromDictionary:@{@"id":@"beaconId"}];
   
    RKObjectMapping *bboxMapping = [RKObjectMapping mappingForClass:[BBoxResponse class] ];

    [beaconsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"value"
                                                                                   toKeyPath:@"value"
                                                                                 withMapping:beaconValueMapping]];
   
    [beaconsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"geometry"
                                                                                   toKeyPath:@"geometry"
                                                                                 withMapping:beaconGeometryMapping]];
   
    RKRelationshipMapping *rowsRel = [RKRelationshipMapping relationshipMappingFromKeyPath:@"rows" toKeyPath:@"rows" withMapping:beaconsMapping];
   
    [bboxMapping addPropertyMapping:rowsRel];
   
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:bboxMapping method:RKRequestMethodAny pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:5984/places/_design/main/_spatial/points?bbox=0,-10,50,50"]];
    RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[responseDescriptor]];
    [operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *result) {
        BBoxResponse *bboxResponse = [result firstObject];
        NSLog(@"Mapped the bbox response: %@", bboxResponse);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        NSLog(@"Failed with error: %@", [error localizedDescription]);
    }];
    [operation start];

as you can see we define mappings for 4 objects, the BBoxResponse, IBeacon, IBeaconValue and IBeaconGeometry. The BBoxResponse is the root object in our object graph, it contains an array of IBeacon objects and each IBeacon object contains in addition IBeaconValue and IBeaconGeometry objects. The simple fields like NSStrings are mapped directly.

When mapping simple fields we can use ‘addAttributeMappingsFromArray’ if the names match, otherwise we can map one name onto the other using ‘addAttributeMappingsFromDictionary’ method.

The relationships between the objects mapped are set with ‘RKRelationshipMapping’. As my BBoxResponse class doesn’t implement any proper description method you can just set the breakpoint in the line where it should be printed by NSLog and inspect it. Works like a charm.

Leave a Reply