Forked from markd2/NSObject+setValuesForKeysWithJSONDictionary.h
Created
July 19, 2013 02:02
-
-
Save nicnocquee/6034545 to your computer and use it in GitHub Desktop.
This file contains 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 | |
// | |
// Created by Tom Harrington on 12/29/11. | |
// Tweaked by Mark Dalrymple | |
// | |
// Copyright (c) 2011 Atomic Bird, LLC. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
// Safe version of -setValuesForKeysWithDictionary that doesn't throw | |
// if there are unexpected keys in the dictionary (which can be bad if you | |
// don't control the web site). | |
// | |
// It walks the properties of the class (and the corresponding ivars) and sees | |
// if they're in the pased-in "json" dictionary. If so, the values are set onto | |
// |self| via KVC. Any values in the dictionary that don't have corresponding | |
// properties/ivars are not even considered. | |
@interface NSObject (XXSetValuesForKeysWithJSONDictionary) | |
- (void) xx_setValuesForKeysWithJSONDictionary: (NSDictionary *) keyedValues | |
dateFormatter: (NSDateFormatter *) dateFormatter; | |
- (void) xx_setValuesForKeysWithJSONDictionary: (NSDictionary *) keyedValues; | |
@end |
This file contains 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 | |
// | |
// Created by Tom Harrington on 12/29/11. | |
// Tweaked by Mark Dalrymple | |
// | |
// Copyright (c) 2011 Atomic Bird, LLC. All rights reserved. | |
// | |
#import "NSObject+setValuesForKeysWithJSONDictionary.h" | |
#import <objc/runtime.h> | |
@implementation NSObject (XXSetValuesForKeysWithJSONDictionary) | |
// Alternative implementation - class_getProperty, driven by the keyedValues | |
- (void) xx_setValuesForKeysWithJSONDictionary: (NSDictionary *) keyedValues | |
dateFormatter: (NSDateFormatter *) dateFormatter { | |
// Walk the current class, as well as superclasses. The property or ivar | |
// to set might be up in the inheritance chain. | |
for (Class clas = [self class]; clas; clas = class_getSuperclass(clas)) { | |
// Walk the properties seeing if any of the properties appear in | |
// the dictionary of JSON values. | |
unsigned int propertyCount; | |
objc_property_t *properties = class_copyPropertyList(clas, &propertyCount); | |
for (unsigned int i = 0; i < propertyCount; i++) { | |
objc_property_t property = properties[i]; | |
// See if the property name is being used in the JSON dictionary. | |
// If so, get the current value. | |
NSString *keyName = @( property_getName(property) ); | |
id value = [keyedValues objectForKey:keyName]; | |
// If not, see if it's the backing ivar name being supplied | |
// in the JSON dictionary. | |
if (value == nil) { | |
// Pull the ivar name out of the property. | |
char *ivarPropertyName = property_copyAttributeValue(property, "V"); | |
// Make sure we're not dealing with a @dynamic property | |
if (ivarPropertyName != NULL) { | |
// See if that lives in the incoming dictionary. | |
// We don't need to change the value of keyName to match the | |
// ivar because it already matches the property, and | |
// setValue:forKey: of that property name will Just Work. | |
NSString *ivarName = @( ivarPropertyName ); | |
value = [keyedValues objectForKey: ivarName]; | |
} | |
free (ivarPropertyName); | |
} | |
if (value == nil) { | |
// This property doesn't have anything in the incoming dictionary. | |
continue; | |
} | |
// otherwise, process the value | |
char *typeEncoding = property_copyAttributeValue(property, "T"); | |
// No type available, so we're done. | |
if (typeEncoding == NULL) { | |
continue; | |
} | |
// We got here because this property has a corresponding new chunk of | |
// data in the json dictionary. Time to put it into |self|. | |
// We might have to morph the incoming value (such as a provided number, | |
// but it needs to converted to a string). | |
switch (typeEncoding[0]) { | |
case '@': { | |
// Object | |
Class propertyClass = nil; | |
size_t typeEncodingLength = strlen(typeEncoding); | |
if (typeEncodingLength >= 3) { | |
char *className = strndup (typeEncoding + 2, | |
typeEncodingLength - 3); | |
propertyClass = NSClassFromString (@(className)); | |
free (className); | |
} | |
// Check for type mismatch and attempt to compensate. | |
if ([propertyClass isSubclassOfClass:[NSString class]] | |
&& [value isKindOfClass:[NSNumber class]]) { | |
// number converted into a string. | |
value = [value stringValue]; | |
} else if ([propertyClass isSubclassOfClass:[NSNumber class]] | |
&& [value isKindOfClass:[NSString class]]) { | |
// String converted into a number. We can't tell what its | |
// intention ls (float, integer, etc), so let the number | |
// formatter make a best guess for us. | |
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; | |
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; | |
value = [numberFormatter numberFromString:value]; | |
} else if (dateFormatter | |
&& [propertyClass isSubclassOfClass:[NSDate class]] | |
&& [value isKindOfClass:[NSString class]]) { | |
// If the caller provided a date formatter, try converting | |
// the date into a string. | |
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]; | |
} | |
break; | |
case 'c': // char | |
case 'C': // unsigned char | |
if ([value isKindOfClass:[NSString class]]) { | |
char firstCharacter = (char)[value characterAtIndex:0]; | |
value = [NSNumber numberWithChar:firstCharacter]; | |
} | |
break; | |
default: | |
break; | |
} | |
if (value) { | |
[self setValue: value forKey: keyName]; | |
} | |
free(typeEncoding); | |
} | |
free (properties); | |
} | |
} // setValues with dateFormatter | |
- (void) xx_setValuesForKeysWithJSONDictionary: (NSDictionary *) keyedValues { | |
[self xx_setValuesForKeysWithJSONDictionary:keyedValues dateFormatter:nil]; | |
} // setValues | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment