CollectionFactory 1.3.0

CollectionFactory 1.3.0

TestsTested
LangLanguage Obj-CObjective C
License MIT
ReleasedLast Release Oct 2016

Maintained by Elliot Chance.



  • By
  • Elliot Chance

Translation between native collections in Objective-C and JSON.

Static methods always return nil if an error occurs (such as JSON could not be passed, was nil, or was an invalid expected type).

Converting to JSON

Native Types

You can use JSONString or JSONData to get the NSString or NSData encoded versions in JSON respectively.

NSDictionary *d = @{@"foo": @"bar"};

// {"foo":"bar"}
NSString *JSONString = [d JSONString];

// The same value as above but as a NSData
NSData *JSONData = [d JSONData];

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

Custom Types

You may also convert any subclass of NSObject:

@interface SomeObject : NSObject

@property NSString *string;
@property int number;

@end
SomeObject *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",
    };
}

@end
SomeObject *myObject = [SomeObject new];
myObject.string = @"foo";
myObject.number = 123;

// {"number":123,"secret":"bar"}
NSString *json = [myObject JSONString];

Converting from JSON

Native Types

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):

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];

Custom Types

Let's say you have this:

@interface SomeObject : NSObject

@property NSString *string;
@property int number;

@end

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

NSString *json = @"{\"string\":\"foo\",\"number\":123};

SomeObject *myObject = [SomeObject objectWithJSONString:json];

// 123
myObject.number;

// Do NOT do this. Otherwise you will get an NSDictionary.
// SomeObject *myObject = [NSObject objectWithJSONString:json];

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: This is is a wrapper for [setValue:forKey:] and will call [setValue:forKey:] 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];
    }
}

@end

Pretty Printing

By default serialized JSON will not contain extra spaces which is best for transmission and storage but it is less readable for debugging. You can generate pretty JSON by specifying the indent size:

NSDictionary *object = @{@"abc": @"def"};

// {
//   "abc": "def"
// }
NSString *result = [object prettyJSONStringWithIndentSize:2];

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"];

Handling Errors

In most cases you will not run into problems. However, the default behavior is to return nil and have the error reason suppressed. If you want to handle the error more safely you can provide a reference to an NSError:

NSError *error;
NSString *result = [object JSONStringOrError:&error];
if (error) {
    // Use these:
    //   error.localizedDescription
    //   error.localizedFailureReason
}

This works the same way for JSONDataOrError: and JSONDictionaryOrError:.