Created
January 11, 2012 02:35
-
-
Save atomicbird/1592634 to your computer and use it in GitHub Desktop.
NSObject category for handling JSON dictionaries. Described in detail at http://www.cimgf.com/2012/01/11/handling-incoming-json-redux/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Correct, my mistake.