Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist

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

View NSObject+setValuesForKeysWithJSONDictionary.h
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
View NSObject+setValuesForKeysWithJSONDictionary.h
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
View NSObject+setValuesForKeysWithJSONDictionary.h
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
View NSObject+setValuesForKeysWithJSONDictionary.h
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

Owner

Correct, my mistake.

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

Owner

Folks,

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.