解析完成后我们可以得到一个这种的类型:
@interface JDCClassProperty : NSObject @property (nonatomic,assign,readonly) objc_property_t property;//c结构property @property (nonatomic,assign,readonly) JDCClassPropertyType propertyType;//property的类型 @property (nonatomic,copy,readonly) NSString *propertyTypeName;//property的类名(比如NSString) @property (nonatomic,copy,readonly) NSString *propertyName;//property的名字(keypath) @property (nonatomic,assign,readonly) BOOL isArray;(用于标记是否为array类型) @property (nonatomic,assign,readonly) BOOL isReadyOnly;(是否为只读类型) - (id)initWithProperty:(objc_property_t)property; @end 映射的实现拿到了所有property的必要信息以后,需要做的就行把NSDictionary的值复制到我们的Model上面,这是一个递归的过程.
我先用伪代码展示一下主要过程。
id initWithDic(dic){ //遍历当前class的所有property for(property in class.properties){ //如果是自定义类型,则使用自定义类继续序列化,之后使用KVC赋值。 if(property.isCustomType){ Class CustomClass = NSClassFromString(property.typeName); id custom = CustomClass.initWithDic(dic[property.name]); self.setValueForKey(custom,property.name); }else{ self.setValueForKey(dic[property.name],property.name); } } }在实际实现代码之前我们需要几个模板方法
//通过这个方法来定义keypath->jsonkey的映射 //比如@property (nonatomic,strong)NSString *aid; // @{@"id":"123"}, 我们可以这样实现: //+(NSDictionary *)jdc_jsonSerializationKeyMapper{ // return @{@"aid":@"id"}; //} +(NSDictionary *)jdc_jsonSerializationKeyMapper; //通过这个方法来确定array的item对应的类型,如果property //是NSArray我们需要用这个方法来制定自定义类。例如: //比如@property (nonatomic,strong)NSArray *items; // 我们可以这样实现: // +(NSDictionary *)jdc_KeyPathToClassNameMapper{ // return @{@"items":@"CustomClassName"}; //} +(NSDictionary *)jdc_KeyPathToClassNameMapper;下面我们来看看实际的映射代码:
- (id)initWithJsonDictionary:(NSDictionary *)jsonDictionary error:(NSError **)error { self = [self init]; NSDictionary *keyPathToJsonKey = [[self class] jdc_jsonSerializationKeyMapper]; NSDictionary *keyPathToClass = [[self class] jdc_KeyPathToClassNameMapper]; NSArray *propertis = [[self class] jdc_classProperties]; for(JDCClassProperty *property in propertis){ //Get json value for mapped keypath id value = [self getJsonValue:property.propertyName jsonKeyPath:keyPathToJsonKey[property.propertyName] dictionary:jsonDictionary]; if (!value) { continue; } switch (property.propertyType) { case JDCClassPropertyStandandJsonType:{ NSString *itemClassName = keyPathToClass[property.propertyName]; if (property.isArray && itemClassName) { Class itemClass = NSClassFromString(itemClassName); NSArray *values = [itemClass modelsFromJsonArray:value error:error]; if (error) { return nil; } [self setValue:values forKey:property.propertyName]; }else{ [self setValue:value forKey:property.propertyName]; } } break; case JDCClassPropertyCustomType:{ Class customClass = NSClassFromString(property.propertyTypeName); id tValue = [[customClass alloc] initWithJsonDictionary:value error:error]; [self setValue:tValue forKey:property.propertyName]; } break; case JDCClassPropertyOtherType:{ @throw [NSException exceptionWithName:@"unsupported json type" reason:@"NSString, NSNumber, NSArray, NSDictionary and custom Class" userInfo:nil]; } break; default:{ [self setValue:value forKey:property.propertyName]; } break; } } return self; } - (id)getJsonValue:(NSString *)keyPath jsonKeyPath:(NSString *)jsonKeyPath dictionary:(NSDictionary *)dicionary { if (jsonKeyPath) { return [dicionary valueForKeyPath:jsonKeyPath]; }else{ return [dicionary valueForKeyPath:keyPath]; } }到这里JSON map核心代码已经完成了。我们已经实现了一个简单可用的JSON映射框架。另外我还实现了toDictionary功能,思路是差不多的,具体可以参考代码, 完整代码github 。
额外实现NSCodingNSKeyedArchiver的对象序列化功能平时也会经常用到,但是手写encode和decode方法有点烦,明明都是类似的代码为什么要一直重复的呢。我们可以利用之前的实现的基础用几行代码实现NSKeyedArchiver的encode和decode,我们只需要添加一个NSObject Category即可:
@implementation NSObject (JDCNSCoding) - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [self init]) { NSArray *propertis = [[self class] jdc_classProperties]; for(JDCClassProperty *property in propertis){ if (property.propertyType == JDCClassPropertyOtherType) { @throw [NSException exceptionWithName:@"unsupported json type" reason:@"decoding failed" userInfo:nil]; } id value = [aDecoder decodeObjectForKey:property.propertyName]; [self setValue:value forKey:property.propertyName]; } } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { NSArray *propertis = [[self class] jdc_classProperties]; for(JDCClassProperty *property in propertis){ if (property.propertyType == JDCClassPropertyOtherType) { @throw [NSException exceptionWithName:@"unsupported json type" reason:@"encoding failed" userInfo:nil]; } id value = [self valueForKey:property.propertyName]; [aCoder encodeObject:value forKey:property.propertyName]; } } @end需要注意的是这里支持的类型是有限的,具体支持的类型可以参看JDCClassProperty的头文件。
总结