Skip to content

Instantly share code, notes, and snippets.

@elazarl
Created March 6, 2016 14:16
Embed
What would you like to do?
Serializing dictionary to NSObjects, a la Go's json package
#import <Foundation/Foundation.h>
@interface POD : NSObject<NSCopying>
-(nonnull NSString *)description;
-(nonnull NSDictionary *)toDictionary;
+(nonnull id)fromDictionary:(nonnull NSDictionary *)dict;
-(nonnull id)copyWithZone:(nullable NSZone *)zone;
@end
#import "POD.h"
#import "objc/runtime.h"
@implementation POD
-(NSString *)description {
return [[[self class] description] stringByAppendingString:[[self toDictionary] description]];
}
-(NSDictionary *)toDictionary {
NSMutableDictionary *d = [NSMutableDictionary dictionary];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for(i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if (!propName)
continue;
NSString *key = [NSString stringWithUTF8String:propName];
if ([self valueForKey:key] == nil)
continue;
[d setObject:[POD clone:[self valueForKey:key]] forKey:key];
}
free(properties);
return d;
}
+(NSArray<NSString *> *)typeMethodToType:(NSString *)name {
static NSRegularExpression *regex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
regex = [NSRegularExpression
regularExpressionWithPattern:@"_T_(.*)_T_(.*)"
options:0
error:nil];
});
NSTextCheckingResult *r = [regex firstMatchInString:name options:0 range:NSMakeRange(0, [name length])];
if (!r)
return nil;
return @[[name substringWithRange:[r rangeAtIndex:1]],
[name substringWithRange:[r rangeAtIndex:2]]];
}
+(NSDictionary<NSString *, NSString *> *)typeMethods {
unsigned int methodCount = 0;
const char *cls = [NSStringFromClass([self class]) cStringUsingEncoding:NSUTF8StringEncoding];
Method *methods = class_copyMethodList(objc_getMetaClass(cls), &methodCount);
NSMutableDictionary *rv = [NSMutableDictionary dictionary];
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
NSString *name = [NSString stringWithUTF8String:sel_getName(method_getName(method))];
NSArray<NSString *> *methodAndType = [self typeMethodToType:name];
if (methodAndType)
rv[methodAndType[0]] = methodAndType[1];
}
free(methods);
return rv;
}
+(id)fromDictionary:(NSDictionary *)dict {
unsigned int outCount, i;
POD *obj = [[[self class] alloc] init];
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
NSDictionary<NSString *, NSString *> *types = [self typeMethods];
for(i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
const char *propAttr = property_getAttributes(property);
if (!propName || !propAttr)
continue;
if (strncmp(propAttr, "T@\"", 3) != 0)
continue;
propAttr = propAttr+3;
char *end = strchr(propAttr, '"');
NSString *key = [NSString stringWithUTF8String:propName];
NSString *type = [[NSString alloc] initWithBytes:propAttr length:(end-propAttr) encoding:NSUTF8StringEncoding];
NSObject *value;
Class typeClass = NSClassFromString(type);
if ([type isEqualToString:@"NSArray"]) {
NSString *inner = types[key];
if (!inner)
@throw [NSException exceptionWithName:@"IllegalState"
reason:[NSString stringWithFormat:@"cannot find inner type of type %@", key]
userInfo:nil];
NSMutableArray *ar = [NSMutableArray arrayWithCapacity:[dict[key] count]];
Class cls = NSClassFromString(inner);
for (id obj in dict[key]) {
if ([cls isSubclassOfClass:[POD class]])
[ar addObject:[cls fromDictionary:obj]];
else
[ar addObject:obj];
}
value = ar;
} else if ([typeClass isSubclassOfClass: [POD class]])
value = [typeClass fromDictionary:dict[key]];
else
value = dict[key];
[obj setValue:value forKey:key];
}
free(properties);
return obj;
}
+(id)clone:(id)x {
if ([x isKindOfClass:[POD class]])
return [x toDictionary];
if ([x isKindOfClass:[NSArray class]])
return [POD clonePODArray:x];
if ([x isKindOfClass:[NSDictionary class]])
return [POD clonePODDictionary:x];
return x;
}
+(NSDictionary *)clonePODDictionary:(NSDictionary *)dict {
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:dict];
for (id key in d) {
id val = d[key];
if ([val isKindOfClass:[POD class]]) {
d[key] = [val toDictionary];
}
}
return d;
}
+(NSArray *)clonePODArray:(NSArray *)ar {
NSMutableArray *a = [NSMutableArray arrayWithArray:ar];
for (int i=0; i<[a count]; i++) {
id val = a[i];
if ([val isKindOfClass:[POD class]]) {
a[i] = [val toDictionary];
}
}
return a;
}
- (id)copyWithZone:(nullable NSZone *)zone {
NSObject *clone = [[[self class] alloc] init];
[clone setValuesForKeysWithDictionary:[self toDictionary]];
return clone;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment