Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save juliengrimault/2055538 to your computer and use it in GitHub Desktop.

Select an option

Save juliengrimault/2055538 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/
//
// NSObject+setValuesForKeysWithJSONDictionary.h
// SafeSetDemo
//
// Created by Tom Harrington on 12/29/11.
// Copyright (c) 2011 Atomic Bird, LLC. All rights reserved.
//
// Modified by Julien Grimault
//
#import <Foundation/Foundation.h>
@interface NSObject (setValuesForKeysWithJSONDictionary)
/*
for each property of self, a lookup is made on the |propertiesMapping| dictionary to get the key to use in the |keyedValues| dictionary. If no entry is found in propertiesMapping, the property name is used directly.
propertiesMapping uses the property name as key and the keyedValues key as value.
*/
- (void)setValuesForKeysWithJSONDictionary:(NSDictionary *)keyedValues
propertiesMapping:(NSDictionary*)propertiesMapping
dateFormatter:(NSDateFormatter *)dateFormatter;
- (void)setValuesForKeysWithJSONDictionary:(NSDictionary *)keyedValues
dateFormatter:(NSDateFormatter *)dateFormatter;
@end
//
// NSObject+setValuesForKeysWithJSONDictionary.m
// SafeSetDemo
//
// Created by Tom Harrington on 12/29/11.
// Copyright (c) 2011 Atomic Bird, LLC. All rights reserved.
//
// Modified by Julien Grimault
//
#import "NSObject+setValuesForKeysWithJSONDictionary.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation NSObject (setValuesForKeysWithJSONDictionary)
- (void)setValuesForKeysWithJSONDictionary:(NSDictionary *)keyedValues
dateFormatter:(NSDateFormatter *)dateFormatter
{
return [self setValuesForKeysWithJSONDictionary:keyedValues
propertiesMapping:nil
dateFormatter:dateFormatter];
}
- (void)setValuesForKeysWithJSONDictionary:(NSDictionary *)keyedValues
propertiesMapping:(NSDictionary*)propertiesMapping
dateFormatter:(NSDateFormatter *)dateFormatter
{
//we need to go up the inheritance tree to assign the properties declared in the superclass
//we must however assign the properties starting with the root class and ending with [self class],
//this is to ensure that the overriden properties are assigned correctly
NSMutableArray* inheritanceTree = [NSMutableArray array];
Class currentClass = [self class];
while (currentClass) {
[inheritanceTree addObject:currentClass];
currentClass = class_getSuperclass(currentClass);
}
[inheritanceTree enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Class objectClass, NSUInteger idx, BOOL* stop){
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList(objectClass, &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];
//apply mapping
NSString *keyNameJSON = keyName; //default if there is no mapping provided
if (propertiesMapping) {
NSString* mapping = [propertiesMapping objectForKey:keyName];
if (mapping) {
keyNameJSON = mapping;
}
}
id value = [keyedValues objectForKey:keyNameJSON];
if (value != nil) {
char *typeEncoding = NULL;
typeEncoding = property_copyAttributeValue(property, "T");
if (typeEncoding == NULL) {
continue;
}
switch (typeEncoding[0]) {
case '@':
{
// Object
Class propertyClass = nil;
if (strlen(typeEncoding) >= 3) {
char *propertyClassName = strndup(typeEncoding+2, strlen(typeEncoding)-3);
propertyClass = NSClassFromString([NSString stringWithUTF8String:propertyClassName]);
free(propertyClassName);
}
// Check for type mismatch, attempt to compensate
if ([propertyClass isSubclassOfClass:[NSString class]] && [value isKindOfClass:[NSNumber class]]) {
value = [value stringValue];
} else if ([propertyClass isSubclassOfClass:[NSNumber class]] && [value isKindOfClass:[NSString class]]) {
//check if the value is yes,true,no or false..
if ([value caseInsensitiveCompare:@"yes"] == NSOrderedSame ||
[value caseInsensitiveCompare:@"true"] == NSOrderedSame) {
value = [NSNumber numberWithBool:YES];
} else if ([value caseInsensitiveCompare:@"no"] == NSOrderedSame ||
[value caseInsensitiveCompare:@"false"] == NSOrderedSame) {
value = [NSNumber numberWithBool:NO];
} else {
// 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];
}
} else if ([propertyClass isSubclassOfClass:[NSDate class]] && [value isKindOfClass:[NSString class]]) {
if (dateFormatter) {
value = [dateFormatter dateFromString:value];
} else {
NSTimeInterval interval = [value doubleValue];
if (interval != 0.0) {
value = [NSDate dateWithTimeIntervalSince1970:interval];
}
}
} else if ([propertyClass isSubclassOfClass:[NSURL class]] && [value isKindOfClass:[NSString class]]) {
value = [NSURL URLWithString: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];
}
break;
}
case 'c': // char
case 'C': // unsigned char
{
if ([value isKindOfClass:[NSString class]]) {
char firstCharacter = [value characterAtIndex:0];
value = [NSNumber numberWithChar:firstCharacter];
}
break;
}
default:
{
DDLogError(@"Unknown type engonding %c",typeEncoding[0]);
break;
}
}
[self setValue:value forKey:keyName];
free(typeEncoding);
}
}
free(properties);
}];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment