NSObject category for handling JSON dictionaries. Described in detail at http://www.cimgf.com/2012/01/11/handling-incoming-json-redux/

  • Download Gist
NSObject+setValuesForKeysWithJSONDictionary.h
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
//
// NSObject+setValuesForKeysWithJSONDictionary.h
// SafeSetDemo
//
// Created by Tom Harrington on 12/29/11.
// Copyright (c) 2011 Atomic Bird, LLC. All rights reserved.
//
 
#import <Foundation/Foundation.h>
 
@interface NSObject (setValuesForKeysWithJSONDictionary)
 
- (void)setValuesForKeysWithJSONDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter;
 
@end
NSObject+setValuesForKeysWithJSONDictionary.m
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
//
// NSObject+setValuesForKeysWithJSONDictionary.m
// SafeSetDemo
//
// Created by Tom Harrington on 12/29/11.
// Copyright (c) 2011 Atomic Bird, LLC. All rights reserved.
//
 
#import "NSObject+setValuesForKeysWithJSONDictionary.h"
#import <objc/runtime.h>
 
@implementation NSObject (setValuesForKeysWithJSONDictionary)
 
- (void)setValuesForKeysWithJSONDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter
{
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
/*
This code iterates over self's properties instead of ivars because the backing ivar might have a different name
than the property, for example if the class includes something like:
@synthesize foo = foo_;
In this case what we really want is "foo", not "foo_", since the incoming keys in keyedValues probably
don't have the underscore. Looking through properties gets "foo", looking through ivars gets "foo_".
*/
for (int i=0; i<propertyCount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSString *keyName = [NSString stringWithUTF8String:propertyName];
id value = [keyedValues objectForKey:keyName];
if (value != nil) {
char *typeEncoding = NULL;
typeEncoding = property_copyAttributeValue(property, "T");
if (typeEncoding == NULL) {
continue;
}
switch (typeEncoding[0]) {
case '@':
{
// Object
Class class = nil;
if (strlen(typeEncoding) >= 3) {
char *className = strndup(typeEncoding+2, strlen(typeEncoding)-3);
class = NSClassFromString([NSString stringWithUTF8String:className]);
}
// Check for type mismatch, attempt to compensate
if ([class isSubclassOfClass:[NSString class]] && [value isKindOfClass:[NSNumber class]]) {
value = [value stringValue];
} else if ([class isSubclassOfClass:[NSNumber class]] && [value isKindOfClass:[NSString class]]) {
// If the ivar is an NSNumber we really can't tell if it's intended as an integer, float, etc.
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
value = [numberFormatter numberFromString:value];
[numberFormatter release];
} else if ([class isSubclassOfClass:[NSDate class]] && [value isKindOfClass:[NSString class]] && (dateFormatter != nil)) {
value = [dateFormatter dateFromString:value];
}
break;
}
case 'i': // int
case 's': // short
case 'l': // long
case 'q': // long long
case 'I': // unsigned int
case 'S': // unsigned short
case 'L': // unsigned long
case 'Q': // unsigned long long
case 'f': // float
case 'd': // double
case 'B': // BOOL
{
if ([value isKindOfClass:[NSString class]]) {
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
value = [numberFormatter numberFromString:value];
[numberFormatter release];
}
break;
}
case 'c': // char
case 'C': // unsigned char
{
if ([value isKindOfClass:[NSString class]]) {
char firstCharacter = [value characterAtIndex:0];
value = [NSNumber numberWithChar:firstCharacter];
}
break;
}
default:
{
break;
}
}
[self setValue:value forKey:keyName];
free(typeEncoding);
}
}
free(properties);
}
 
@end
NSObjectsetValuesForKeysWithJSONDictionary.h
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
//
// NSObject+setValuesForKeysWithJSONDictionary.h
// SafeSetDemo
//
// Created by Tom Harrington on 12/29/11.
// Copyright (c) 2011 Atomic Bird, LLC. All rights reserved.
//
 
#import <Foundation/Foundation.h>
 
@interface NSObject (setValuesForKeysWithJSONDictionary)
 
- (void)setValuesForKeysWithJSONDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter;
 
@end
NSObjectsetValuesForKeysWithJSONDictionary.m
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
//
// NSObject+setValuesForKeysWithJSONDictionary.m
// SafeSetDemo
//
// Created by Tom Harrington on 12/29/11.
// Copyright (c) 2011 Atomic Bird, LLC. All rights reserved.
//
 
#import "NSObject+setValuesForKeysWithJSONDictionary.h"
#import <objc/runtime.h>
 
@implementation NSObject (setValuesForKeysWithJSONDictionary)
 
- (void)setValuesForKeysWithJSONDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter
{
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
/*
This code iterates over self's properties instead of ivars because the backing ivar might have a different name
than the property, for example if the class includes something like:
@synthesize foo = foo_;
In this case what we really want is "foo", not "foo_", since the incoming keys in keyedValues probably
don't have the underscore. Looking through properties gets "foo", looking through ivars gets "foo_".
*/
for (int i=0; i<propertyCount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSString *keyName = [NSString stringWithUTF8String:propertyName];
id value = [keyedValues objectForKey:keyName];
if (value != nil) {
char *typeEncoding = NULL;
typeEncoding = property_copyAttributeValue(property, "T");
if (typeEncoding == NULL) {
continue;
}
switch (typeEncoding[0]) {
case '@':
{
// Object
Class class = nil;
if (strlen(typeEncoding) >= 3) {
char *className = strndup(typeEncoding+2, strlen(typeEncoding)-3);
class = NSClassFromString([NSString stringWithUTF8String:className]);
free(className);
}
// Check for type mismatch, attempt to compensate
if ([class isSubclassOfClass:[NSString class]] && [value isKindOfClass:[NSNumber class]]) {
value = [value stringValue];
} else if ([class isSubclassOfClass:[NSNumber class]] && [value isKindOfClass:[NSString class]]) {
// If the ivar is an NSNumber we really can't tell if it's intended as an integer, float, etc.
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
value = [numberFormatter numberFromString:value];
[numberFormatter release];
} else if ([class isSubclassOfClass:[NSDate class]] && [value isKindOfClass:[NSString class]] && (dateFormatter != nil)) {
value = [dateFormatter dateFromString:value];
}
break;
}
case 'i': // int
case 's': // short
case 'l': // long
case 'q': // long long
case 'I': // unsigned int
case 'S': // unsigned short
case 'L': // unsigned long
case 'Q': // unsigned long long
case 'f': // float
case 'd': // double
case 'B': // BOOL
{
if ([value isKindOfClass:[NSString class]]) {
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
value = [numberFormatter numberFromString:value];
[numberFormatter release];
}
break;
}
case 'c': // char
case 'C': // unsigned char
{
if ([value isKindOfClass:[NSString class]]) {
char firstCharacter = [value characterAtIndex:0];
value = [NSNumber numberWithChar:firstCharacter];
}
break;
}
default:
{
break;
}
}
[self setValue:value forKey:keyName];
free(typeEncoding);
}
}
free(properties);
}
 
@end

Line 47 is a memory leak. You need to add a free(className) at line 48.5

Correct, my mistake.

If I could make one more suggestion....using NSDateFormatter is generally evil due to time zones. Suggest replacing it with this:

                struct tm atime;
                memset(&atime, 0, sizeof(atime));
                (void)strptime_l([value UTF8String], [format UTF8String], &atime, NULL);
                value = [NSDate dateWithTimeIntervalSince1970:mktime(&atime)];

where 'format' is an NSString passed into the method instead of a dateFormatter.

Thank you SO much for this post. I love this routine and plan to incorporate it into almost every one of my projects. Nice job!

On Jan 11, 2012, at 11:39 AM, Tom Harrington wrote:

Correct, my mistake.


Reply to this email directly or view it on GitHub:
https://gist.github.com/1592634

I've found this NSFormatter subclass helps with timezone issues: http://boredzo.org/iso8601dateformatter/

Is this offered under any specific licence? Thank you.

case 'B': // BOOL

This is incorrect. Bool is defined as 'B' whereas BOOL is defined as a char ('c'), meaning this code would read it incorrectly. I'm not sure if there is a way of differentiating the BOOL type from a regular char.

You can see types by typing "p @encode(BOOL)" in Xcode debugger

Folks,

Github doesn't notify me of comments left here. You can reach me on Twitter as @atomicbird if you like.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.