CollectionFactory: JSON and Objective-C

Image for post
Image for post

CollectionFactoryis a CocoaPod I wrote a long time ago to deal with JSON in Objective-C.

There are a lot of other libraries out there that do this kind of thing. However, I wanted something that was:

  1. Very light and transparent without the need to create any intermediate code (such as predefined models).
  2. Easy and interoperable with native types as well as custom objects without modification.

It’s also quite possible that there were other libraries out there that fit the bill, but I just didn’t look hard enough.

Anyway, let’s get started…

Converting to JSON

You can use jsonStringor jsonDatato get the NSStringor NSDataencoded versions in JSON respectively.

NSDictionary *d = @{@"foo": @"bar"};// {"foo":"bar"}
NSString *jsonString = [d jsonString];
// The same value as above but as an NSData
NSData *jsonData = [d jsonData];

Both methods are available on NSNull, NSNumber, NSArray, NSDictionary, NSObject, and NSString.

You may also convert any subclass of NSObject:

@interface SomeObject : NSObject@property NSString *string;
@property int number;
@endSomeObject *myObject = [SomeObject new];
myObject.string = @"foo";
myObject.number = 123;
// {"string":"foo","number":123}
NSString *json = [myObject jsonString];

If you need to control how custom objects are serialized you may override the [jsonDictionary] method:

@implementation SomeObject- (NSDictionary *)jsonDictionary {
return @{
@"number": self.number,
@"secret": @"bar",
@endSomeObject *myObject = [SomeObject new];
myObject.string = @"foo";
myObject.number = 123;
// {"number":123,"secret":"bar"}
NSString *json = [myObject jsonString];

Converting from JSON

The simplest way to convert JSON to an object is to run it through NSObject:

NSString *json = @”{\”foo\”:\”bar\”}”;
id object = [NSObject objectWithJsonString:json];

However, if you know the type of the incoming value you should use the respective class factory (rather than blindly casting) 1:

NSString *json = @”{\”foo\”:\”bar\”}”;
NSDictionary *d = [NSDictionary dictionaryWithJsonString:json];

When using a specific class it will not accept a valid JSON value of an unexpected type to prevent bugs occuring, for example:

NSString *json = @"{\"foo\":\"bar\"}";// `a` is `nil` because we only intend to decode a JSON array.
NSArray *a = [NSArray arrayWithJsonString:json];
// `b` is an instance of `NSDictionary` but future code will be treating it like
// an `NSArray` which will surely cause very bad things to happen...
NSArray *b = [NSObject objectWithJsonString:json];

You can also decode JSON into existing objects like:

NSString *json = @"{\"string\":\"foo\",\"number\":123};SomeObject *myObject = [SomeObject objectWithJsonString:json];// 123
// Do NOT do this. Otherwise you will get an NSDictionary.
// SomeObject *myObject = [NSObject objectWithJsonString:json];

The same method that unwraps native types is used except because the static method [objectWithJsonString:] is called against SomeObjectyou are saying that it must unserialize to that type of object.

¹ Static methods always return nil if an error occurs (such as the JSON could not be parsed, was nil, or was an unexpected type).

Objects are contructed recursively by first checking to see if the property exists, if it does and the data is not prefixed with NS it will create another custom object and continue. This means JSON can be used to unpack simple objects without any specific code however this has some caveats:

  1. It is dangerous. Not all properties are public or even exist so types can be easily missing and cause serious memory error when trying to use the unpacked objects.
  2. It will always use the [init]constructor which may be wrong or not even available making the object constructions impossible.

A much safer way to unpack objects is to override the [setValue:forProperty:] method. This allows you to control exactly what logic you need with each property.

Note: There is a wrapper for [setValue:forKey:]and will call [setValue:forProperty:]if you have not overridden it.

@implementation SomeObject- (void)setValue:(id)value forProperty:(NSString *)key
// Only allow these two properties to be set.
NSArray *properties = @[@"number", @"string"];
if ([properties indexOfObject:key] != NSNotFound) {
[self setValue:value forKey:key];

Creating Mutable Objects

For every factory method there is a mutable counterpart used for generating objects that be safely editly directly after unpacking.

NSString *json = @"{\"foo\":\"bar\"}";
NSDictionary *d = [NSDictionary dictionaryWithJsonString:json];
NSMutableDictionary *md = [NSMutableDictionary mutableDictionaryWithJsonString:json];

Loading from Files

Each factory method also has a way to generate the object directly from a file:

NSArray *foo = [NSArray arrayWithJsonFile:@”foo.json”];

If the file does not exist, there was an error parsing or the JSON was the wrong type then nil will be returned.

Futhermore you can create mutable objects from files:

NSMutableArray *foo = [NSMutableArray mutableArrayWithJsonFile:@”foo.json”];

Originally published at on August 13, 2016.

Written by

I’m a data nerd and TDD enthusiast originally from Sydney. Currently working for Uber in New York. My thoughts here are my own. 🤓

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store