一,JSONModel的基本使用
1,基本使用方法
- (instancetype)initWithDictionary:(NSDictionary *)dict error:(NSError **)err;
- (instancetype)initWithData:(NSData *)data error:(NSError **)error;
- (instancetype)initWithString:(NSString *)string error:(JSONModelError **)err;
- (instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError **)err;
这四个方法都是 JSONModel 类中的初始化方法,用于创建并初始化 JSONModel 的实例对象。
使用情况:
initWithDictionary:error:
将一个包含 JSON 对象的字典转换为 JSONModel 实例对象时使用。initWithData:error:
将从从网络获取到 JSON 数据转换为 JSONModel 实例对象时使用。initWithString:error:
将一个 JSON 字符串转换为 JSONModel 实例对象时使用。initWithString:usingEncoding:error:
当您已经有一个 JSON 字符串,并需要指定字符编码时使用。
这些方法提供了不同的输入方式,以适应不同的数据源和数据表示形式。
2,转换属性名称
有时我们获取的josn数据的key发生变化和定义的model对象的属性不一样,而模型属性不能轻易改变。比如原来字典里的gender
这个key变成了sex
,这就需要我们定义一个转换的mapper(JSONKeyMapper):
@implementation Person
+ (JSONKeyMapper *)keyMapper
{return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"gender": @"sex", }];
}
具体效果:
NSDictionary *dict = @{@"name":@"Jack",@"age":@23,@"sex":@"male",};
NSError *error;
Person *person = [[Person alloc] initWithDictionary:dict error:&error];
输出:
[name]: Jack[age]: 23[gender]: male
没有受到传入字典里key值的变化的影响。
3,自定义错误
除了一些框架自己处理的错误(比如传入的对象不是字典),框架的作者也允许我们自己定义属于我们自己的错误。
比如,当age对应的数值小于25
的时候,打印出Too young!,并阻止模型的转换:
在模型的实现文件中:
- (BOOL)validate:(NSError **)error
{if (![super validate:error])return NO;if (self.age < 25){*error = [NSError errorWithDomain:@"Too young!" code:10 userInfo:nil];NSError *errorLog = *error;NSLog(@"%@",errorLog.domain);return NO;}return YES;
}
如果要被转化的数据age小于25,就会打印错误,并且模型也不会转化。
4,模型嵌套
有时,我们需要在模型里加一个数组,而这个数组里面的元素是另一个对象:这就涉及到了模型的嵌套。如果这样做我们需要在模型类中增加一个协议:
#import "JSONModel.h"
@protocol Friend;
@interface Friend : JSONModel
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
@interface Person : JSONModel
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *gender;
@property (nonatomic, strong) NSArray *friends;//数组,嵌套模型
@end
而且要在Person的实现文件里加上这一段代码:
@implementation Friend
@end
不想因为服务器的某个值没有返回(nil)就使程序崩溃, 我们会加关键字Optional
@property (nonatomic, copy) NSString<optional> *name;
如果不想每一条属性都添加,我们也可以在.m文件中重写方法
+ (BOOL) propertyIsOptional:(NSString *)propertyName {return YES;
}
二,源码分析
1,jsonModel的四个基本的使用方法的源码
-(id)initWithString:(NSString*)string error:(JSONModelError**)err
{JSONModelError* initError = nil;id objModel = [self initWithString:string usingEncoding:NSUTF8StringEncoding error:&initError];if (initError && err) *err = initError;return objModel;
}
-(id)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err
{//check for nil inputif (!string) {if (err) *err = [JSONModelError errorInputIsNil];return nil;}JSONModelError* initError = nil;id objModel = [self initWithData:[string dataUsingEncoding:encoding] error:&initError];if (initError && err) *err = initError;return objModel;}
-(instancetype)initWithData:(NSData *)data error:(NSError *__autoreleasing *)err
{//check for nil inputif (!data) {if (err) *err = [JSONModelError errorInputIsNil];return nil;}//read the jsonJSONModelError* initError = nil;id obj = [NSJSONSerialization JSONObjectWithData:dataoptions:kNilOptionserror:&initError];if (initError) {if (err) *err = [JSONModelError errorBadJSON];return nil;}//init with dictionaryid objModel = [self initWithDictionary:obj error:&initError];if (initError && err) *err = initError;return objModel;
}
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{//check for nil inputif (!dict) {if (err) *err = [JSONModelError errorInputIsNil];return nil;}//invalid input, just create empty instanceif (![dict isKindOfClass:[NSDictionary class]]) {if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];return nil;}//create a class instanceself = [self init];if (!self) {//super init didn't succeedif (err) *err = [JSONModelError errorModelIsInvalid];return nil;}//check incoming data structureif (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {return nil;}//import the data from a dictionaryif (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {return nil;}//run any custom model validationif (![self validate:err]) {return nil;}//model is valid! yay!return self;
}
这些方法从上到下依次实现嵌套,最终都实现了
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
2,具体看一看initWithDictionary:error:
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{//方法1. 参数为nilif (!dict) {if (err) *err = [JSONModelError errorInputIsNil];return nil;}//方法2. 参数不是nil,但也不是字典if (![dict isKindOfClass:[NSDictionary class]]) {if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];return nil;}//方法3. 初始化self = [self init];if (!self) {//初始化失败if (err) *err = [JSONModelError errorModelIsInvalid];return nil;}//方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {return nil;}//方法5. 核心方法:字典的key与模型的属性的映射if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {return nil;}//方法6. 可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回if (![self validate:err]) {return nil;}//方法7. 终于通过了!成功返回modelreturn self;
}
这个方法的大致流程如下:
首先,在这个模型类的对象被初始化的时候,遍历自身到所有的父类(直到JSONModel为止),获取所有的属性,并将其保存在一个字典里。获取传入字典的所有key,将这些key与保存的所有属性进行匹配(__doesDictionary:matchModelWithKeyMapper:error:)
,如果匹配成功,则对
将字典的相应值赋值给属性(kvc赋值)(__importDictionary:withKeyMapper:validation:error:)
3,JSONModel所持有的一些关联对象名称:
#pragma mark - associated objects names
static const char * kMapperObjectKey;
static const char * kClassPropertiesKey;
static const char * kClassRequiredPropertyNamesKey;
static const char * kIndexPropertyNameKey;
这些关联对象名称的作用如下:
-
kMapperObjectKey
:与JSON映射器相关联的对象的关联对象键。在这种情况下,它可能用于将JSON映射器对象与类或实例关联起来。JSON映射器通常用于将JSON数据映射到模型对象的属性上。 -
kClassPropertiesKey
:与类属性相关联的对象的关联对象键。在这个上下文中,它可能用于将类属性的信息或描述与类对象关联起来。这些信息可能包括属性名称、类型、访问权限
等。 -
kClassRequiredPropertyNamesKey
:与类的必需属性名称相关联的对象的关联对象键。在这个上下文中,它可能用于将类的必需属性名称与类对象关联起来。必需属性是指在模型对象中必须存在的属性。 -
kIndexPropertyNameKey
:与索引属性名称相关联的对象的关联对象键。在这个上下文中,它可能用于将索引属性名称与类对象关联起来。索引属性通常用于在模型对象集合中标识唯一的对象。
这些关联对象名称的作用是在运行时为类或实例关联附加的元数据或相关信息。通过使用关联对象,可以将自定义的数据与类或实例相关联,以提供额外的功能或元数据,例如属性映射信息、必需属性列表等。这样可以在运行时动态地访问和操作这些附加的信息。
具体的流程:
4,load方法
定义了该框架支持的类型:
+(void)load
{static dispatch_once_t once;dispatch_once(&once, ^{@autoreleasepool {//兼容的对象属性allowedJSONTypes = @[[NSString class], [NSNumber class], [NSDecimalNumber class], [NSArray class], [NSDictionary class], [NSNull class], //immutable JSON classes[NSMutableString class], [NSMutableArray class], [NSMutableDictionary class] //mutable JSON classes];//兼容的基本类型属性allowedPrimitiveTypes = @[@"BOOL", @"float", @"int", @"long", @"double", @"short",//and some famous aliases@"NSInteger", @"NSUInteger",@"Block"];//转换器valueTransformer = [[JSONValueTransformer alloc] init];//自己的类型JSONModelClass = NSClassFromString(NSStringFromClass(self));}});
}
init
方法:
-(id)init
{self = [super init];if (self) {[self __setup__];}return self;
}-(void)__setup__
{//只有第一次实例化时,才执行if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {[self __inspectProperties];}//如果存在自定义的mapper,则将它保存在关联对象里面,key是kMapperObjectKeyid mapper = [[self class] keyMapper];if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {objc_setAssociatedObject(self.class,&kMapperObjectKey,mapper,OBJC_ASSOCIATION_RETAIN // This is atomic);}
}
-(JSONKeyMapper*)__keyMapper
{//get the model key mapperreturn objc_getAssociatedObject(self.class, &kMapperObjectKey);
}
init中的__inspectProperties
方法
-(void)__inspectProperties
{
// 最终保存所有属性的字典,形式为:
// {
// age = "@property primitive age (Setters = [])";
// friends = "@property NSArray*<Friend> friends (Standard JSON type, Setters = [])";
// gender = "@property NSString* gender (Standard JSON type, Setters = [])";
// name = "@property NSString* name (Standard JSON type, Setters = [])";
// }NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];//获取当前的类名Class class = [self class];NSScanner* scanner = nil;NSString* propertyType = nil;// 循环条件:当class 是 JSONModel自己的时候终止while (class != [JSONModel class]) {//属性的个数unsigned int propertyCount;//获得属性列表(所有@property声明的属性)objc_property_t *properties = class_copyPropertyList(class, &propertyCount);//遍历所有的属性for (unsigned int i = 0; i < propertyCount; i++) {//获得属性名称objc_property_t property = properties[i];//获得当前的属性const char *propertyName = property_getName(property);//name(C字符串)//JSONModel里的每一个属性,都被封装成一个JSONModelClassProperty对象JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];p.name = @(propertyName);//propertyName:属性名称,例如:name,age,gender//获得属性类型const char *attrs = property_getAttributes(property);NSString* propertyAttributes = @(attrs);// T@\"NSString\",C,N,V_name// Tq,N,V_age// T@\"NSString\",C,N,V_gender// T@"NSArray<Friend>",&,N,V_friendsNSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];//说明是只读属性,不做任何操作if ([attributeItems containsObject:@"R"]) {continue; //to next property}//检查出是布尔值if ([propertyAttributes hasPrefix:@"Tc,"]) {p.structName = @"BOOL";//使其变为结构体}//实例化一个scannerscanner = [NSScanner scannerWithString: propertyAttributes];[scanner scanUpToString:@"T" intoString: nil];[scanner scanString:@"T" intoString:nil];//http://blog.csdn.net/kmyhy/article/details/8258858if ([scanner scanString:@"@\"" intoString: &propertyType]) {//属性是一个对象[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"]intoString:&propertyType];//propertyType -> NSStringp.type = NSClassFromString(propertyType);// p.type = @"NSString"p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound); //判断是否是可变的对象p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];//是否是该框架兼容的类型//存在协议(数组,也就是嵌套模型)while ([scanner scanString:@"<" intoString:NULL]) {NSString* protocolName = nil;[scanner scanUpToString:@">" intoString: &protocolName];if ([protocolName isEqualToString:@"Optional"]) {p.isOptional = YES;} else if([protocolName isEqualToString:@"Index"]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"p.isIndex = YES;
#pragma GCC diagnostic popobjc_setAssociatedObject(self.class,&kIndexPropertyNameKey,p.name,OBJC_ASSOCIATION_RETAIN // This is atomic);} else if([protocolName isEqualToString:@"Ignore"]) {p = nil;} else {p.protocol = protocolName;}//到最接近的>为止[scanner scanString:@">" intoString:NULL];}}else if ([scanner scanString:@"{" intoString: &propertyType]) {//属性是结构体[scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]intoString:&propertyType];p.isStandardJSONType = NO;p.structName = propertyType;}else {//属性是基本类型:Tq,N,V_age[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]intoString:&propertyType];//propertyType:qpropertyType = valueTransformer.primitivesNames[propertyType];//propertyType:long//基本类型数组if (![allowedPrimitiveTypes containsObject:propertyType]) {//类型不支持@throw [NSException exceptionWithName:@"JSONModelProperty type not allowed"reason:[NSString stringWithFormat:@"Property type of %@.%@ is not supported by JSONModel.", self.class, p.name]userInfo:nil];}}NSString *nsPropertyName = @(propertyName);//可选的if([[self class] propertyIsOptional:nsPropertyName]){p.isOptional = YES;}//可忽略的if([[self class] propertyIsIgnored:nsPropertyName]){p = nil;}//集合类Class customClass = [[self class] classForCollectionProperty:nsPropertyName];if (customClass) {p.protocol = NSStringFromClass(customClass);}//忽略blockif ([propertyType isEqualToString:@"Block"]) {p = nil;}//如果字典里不存在,则添加到属性字典里(终于添加上去了。。。)if (p && ![propertyIndex objectForKey:p.name]) {[propertyIndex setValue:p forKey:p.name];}//setter 和 getterif (p){ //name ->NameNSString *name = [p.name stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[p.name substringToIndex:1].uppercaseString];// getterSEL getter = NSSelectorFromString([NSString stringWithFormat:@"JSONObjectFor%@", name]);if ([self respondsToSelector:getter])p.customGetter = getter;// settersp.customSetters = [NSMutableDictionary new];SEL genericSetter = NSSelectorFromString([NSString stringWithFormat:@"set%@WithJSONObject:", name]);if ([self respondsToSelector:genericSetter])p.customSetters[@"generic"] = [NSValue valueWithBytes:&genericSetter objCType:@encode(SEL)];for (Class type in allowedJSONTypes){NSString *class = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:type]);if (p.customSetters[class])continue;SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@With%@:", name, class]);if ([self respondsToSelector:setter])p.customSetters[class] = [NSValue valueWithBytes:&setter objCType:@encode(SEL)];}}}free(properties);//再指向自己的父类,知道等于JSONModel才停止class = [class superclass];}//最后保存所有当前类,JSONModel的所有的父类的属性objc_setAssociatedObject(self.class,&kClassPropertiesKey,[propertyIndex copy],OBJC_ASSOCIATION_RETAIN);
}
这个方法的作用是检查当前类及其除了JSONModel的所有父类的属性,并将这些属性保存到一个字典中。在将来用于和传入的字典进行映射。
该字典的格式如下:
{propertyName1 = "@property propertyType1 (propertyAttributes1)";propertyName2 = "@property propertyType2 (propertyAttributes2)";...
}
JSONModelClassProperty
类封装了JSONModel
的每一个属性,每个属性都以属性名称作为键,以属性的类型和属性特性作为值进行保存。属性的类型和特性信息是通过扫描属性的属性字符串来获取的。
5,__doesDictionary:matchModelWithKeyMapper:error:
方法
-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{//拿到字典里所有的keyNSArray* incomingKeysArray = [dict allKeys];//返回保存所有属性名称的数组(name,age,gender...)NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;//从array拿到setNSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];//如果用户自定义了mapper,则进行转换if (keyMapper || globalKeyMapper) {NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];NSString* transformedName = nil;//便利需要转换的属性列表for (JSONModelClassProperty* property in [self __properties__]) {//被转换成的属性名称 gender(模型内) -> sex(字典内)transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;//拿到sex以后,查看传入的字典里是否有sex对应的值id value;@try {value = [dict valueForKeyPath:transformedName];}@catch (NSException *exception) {value = dict[transformedName];}//如果值存在,则将sex添加到传入的keys数组中if (value) {[transformedIncomingKeys addObject: property.name];}}incomingKeys = transformedIncomingKeys;}//查看当前的model的属性的集合是否大于传入的属性集合,如果是,则返回错误。//也就是说模型类里的属性是不能多于传入字典里的key的,例如:if (![requiredProperties isSubsetOfSet:incomingKeys]) {//获取多出来的属性[requiredProperties minusSet:incomingKeys];//not all required properties are in - invalid inputJMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];return NO;}//不需要了,释放掉incomingKeys= nil;requiredProperties= nil;return YES;
}
这段代码是一个方法的实现,
- 其作用是检查传入的字典(
NSDictionary
)是否与当前模型类的属性匹配。 - 首先,它获取传入字典的所有键(keys)并保存在
incomingKeysArray
数组中。 - 然后,它获取当前模型类的必需属性名称并保存在
requiredProperties
的可变集合中。 - 接下来,它将
incomingKeysArray
数组转换为集合类型的incomingKeys
。 - 如果存在自定义的键映射器(
keyMapper
)或全局键映射器(globalKeyMapper
),则使用键映射器将属性名称进行转换。它遍历模型类的属性列表,将每个属性的名称根据键映射器进行转换,然后检查传入字典中是否存在转换后的键对应的值。如果存在值,则将原始属性名称添加到transformedIncomingKeys
集合中。 - 最后,它将
transformedIncomingKeys
集合赋值给incomingKeys
,并检查当前模型类的必需属性是否都存在于incomingKeys
集合中。如果有必需属性缺失,则记录错误信息,并返回NO
。如果所有必需属性都存在于incomingKeys
集合中,则返回YES
表示匹配成功。
总之,这段代码用于验证传入字典与当前模型类的属性之间的匹配关系,并返回匹配结果。
如果存在了用户自定义的mapper,则需要按照用户的定义来进行转换。(在这里是将gender转换为了sex)。
6,__importDictionary:withKeyMapper:validation:error:
方法
-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{//遍历保存的所有属性的字典for (JSONModelClassProperty* property in [self __properties__]) {//将属性的名称拿过来,作为key,用这个key来查找传进来的字典里对应的值NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;//用来保存从字典里获取的值id jsonValue;@try {jsonValue = [dict valueForKeyPath: jsonKeyPath];}@catch (NSException *exception) {jsonValue = dict[jsonKeyPath];}//字典不存在对应的keyif (isNull(jsonValue)) {//如果这个key是可以不存在的if (property.isOptional || !validation) continue;//如果这个key是必须有的,则返回错误if (err) {NSString* msg = [NSString stringWithFormat:@"Value of required model key %@ is null", property.name];JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];*err = [dataErr errorByPrependingKeyPathComponent:property.name];}return NO;}//获取 取到的值的类型Class jsonValueClass = [jsonValue class];BOOL isValueOfAllowedType = NO;//查看是否是本框架兼容的属性类型for (Class allowedType in allowedJSONTypes) {if ( [jsonValueClass isSubclassOfClass: allowedType] ) {isValueOfAllowedType = YES;break;}}//如果不兼容,则返回NO,mapping失败if (isValueOfAllowedType==NO) {//type not allowedJMLog(@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass));if (err) {NSString* msg = [NSString stringWithFormat:@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass)];JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];*err = [dataErr errorByPrependingKeyPathComponent:property.name];}return NO;}//如果是兼容的类型:if (property) {// 查看是否有自定义setter,并设置if ([self __customSetValue:jsonValue forProperty:property]) {continue;};// 基本类型if (property.type == nil && property.structName==nil) {//kvc赋值if (jsonValue != [self valueForKey:property.name]) {[self setValue:jsonValue forKey: property.name];}continue;}// 如果传来的值是空,即使当前的属性对应的值不是空,也要将空值赋给它if (isNull(jsonValue)) {if ([self valueForKey:property.name] != nil) {[self setValue:nil forKey: property.name];}continue;}// 1. 属性本身是否是jsonmodel类型if ([self __isJSONModelSubClass:property.type]) {//通过自身的转模型方法,获取对应的值JSONModelError* initErr = nil;id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];if (!value) {//如果该属性不是必须的,则略过if (property.isOptional || !validation) continue;//如果该属性是必须的,则返回错误if((err != nil) && (initErr != nil)){*err = [initErr errorByPrependingKeyPathComponent:property.name];}return NO;}//当前的属性值为空,则赋值if (![value isEqual:[self valueForKey:property.name]]) {[self setValue:value forKey: property.name];}continue;} else {// 如果不是jsonmodel的类型,则可能是一些普通的类型:NSArray,NSString。。。// 是否是模型嵌套(带有协议)if (property.protocol) {//转化为数组,这个数组就是例子中的friends属性。jsonValue = [self __transform:jsonValue forProperty:property error:err];if (!jsonValue) {if ((err != nil) && (*err == nil)) {NSString* msg = [NSString stringWithFormat:@"Failed to transform value, but no error was set during transformation. (%@)", property];JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];*err = [dataErr errorByPrependingKeyPathComponent:property.name];}return NO;}}// 对象类型if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {//可变类型if (property.isMutable) {jsonValue = [jsonValue mutableCopy];}//赋值if (![jsonValue isEqual:[self valueForKey:property.name]]) {[self setValue:jsonValue forKey: property.name];}continue;}// 当前的值的类型与对应的属性的类型不一样的时候,需要查看用户是否自定义了转换器(例如从NSSet到NSArray转换:- (NSSet *)NSSetFromNSArray:(NSArray *)array)if ((![jsonValue isKindOfClass:property.type] && !isNull(jsonValue))||//the property is mutableproperty.isMutable||//custom struct propertyproperty.structName) {// searched around the web how to do this better// but did not find any solution, maybe that's the best idea? (hardly)Class sourceClass = [JSONValueTransformer classByResolvingClusterClasses:[jsonValue class]];//JMLog(@"to type: [%@] from type: [%@] transformer: [%@]", p.type, sourceClass, selectorName);//build a method selector for the property and json object classesNSString* selectorName = [NSString stringWithFormat:@"%@From%@:",(property.structName? property.structName : property.type), //target namesourceClass]; //source nameSEL selector = NSSelectorFromString(selectorName);//查看自定义的转换器是否存在BOOL foundCustomTransformer = NO;if ([valueTransformer respondsToSelector:selector]) {foundCustomTransformer = YES;} else {//try for hidden custom transformerselectorName = [NSString stringWithFormat:@"__%@",selectorName];selector = NSSelectorFromString(selectorName);if ([valueTransformer respondsToSelector:selector]) {foundCustomTransformer = YES;}}//如果存在自定义转换器,则进行转换if (foundCustomTransformer) {IMP imp = [valueTransformer methodForSelector:selector];id (*func)(id, SEL, id) = (void *)imp;jsonValue = func(valueTransformer, selector, jsonValue);if (![jsonValue isEqual:[self valueForKey:property.name]])[self setValue:jsonValue forKey:property.name];} else {//没有自定义转换器,返回错误NSString* msg = [NSString stringWithFormat:@"%@ type not supported for %@.%@", property.type, [self class], property.name];JSONModelError* dataErr = [JSONModelError errorInvalidDataWithTypeMismatch:msg];*err = [dataErr errorByPrependingKeyPathComponent:property.name];return NO;}} else {// 3.4) handle "all other" cases (if any)if (![jsonValue isEqual:[self valueForKey:property.name]])[self setValue:jsonValue forKey:property.name];}}}}return YES;
}
这个方法作用是将传入的NSDictionary对象(dict)中的数据映射到当前对象(self)的属性上,并进行验证和转换。
具体功能如下:
- 遍历当前对象的所有属性(通过[self properties]获取属性列表)。
- 获取属性的名称,并根据keyMapper映射规则将其转换为对应的JSON键路径(jsonKeyPath)。
- 从传入的字典(dict)中根据键路径(jsonKeyPath)获取对应的值(jsonValue)。
- 如果值(jsonValue)为null或字典中不存在对应的键,则根据属性的可选性和验证选项判断是否继续。
- 如果属性是可选的或者验证选项为false,则继续下一个属性的处理。
- 如果属性是必需的且验证选项为true,则返回错误信息。
- 检查获取到的值(jsonValue)的类型是否兼容当前属性的类型。
- 如果值的类型不兼容,则返回错误信息。
- 对于兼容的类型:
- 如果属性存在自定义的
setter方法
,则调用该方法设置值,并继续下一个属性的处理。
- 对于基本类型的属性,使用
KVC
赋值。 - 如果传来的值为空,即使属性的值不为空,也将空值赋给属性。
- 如果属性是JSONModel类型,通过属性的转换方法初始化一个新的对象(value),并将其赋给属性。
- 如果属性是普通的对象类型(如NSArray、NSString等),根据属性的协议进行转换,并赋值给属性。
- 如果属性的值类型与属性类型不一致,检查是否存在自定义的转换器,并进行转换。
- 如果以上情况都不满足,则直接赋值给属性。
- 处理完所有属性后,返回YES表示映射和转换成功。
这段代码的作用是将传入的字典数据映射到当前对象的属性上,并进行验证和类型转换,以实现属性与字典数据的对应关系。
三,总结
1,JSONModel的优点:
- 简化的数据映射:JSONModel提供了简单而直观的方式来将JSON数据映射到Objective-C对象的属性上。开发人员只需定义模型类和属性映射关系,就能轻松地进行数据映射,而无需手动解析和转换JSON数据。
- 自动类型转换:JSONModel能够自动将JSON数据的值转换为合适的Objective-C类型。它支持将JSON数据转换为各种常见的数据类型,如NSString、NSNumber、NSDate等,使开发人员无需手动处理类型转换的细节。
- 数据验证:JSONModel允许开发人员在模型类中定义数据验证规则。通过实现验证方法,可以对属性进行验证,并在验证失败时提供错误信息。这有助于确保从JSON数据中获取的值符合预期,并提高数据的完整性和可靠性。
- 嵌套对象支持:JSONModel支持嵌套的数据结构,可以将一个JSONModel对象作为另一个JSONModel对象的属性。这使得处理复杂的嵌套JSON数据变得简单而直观,可以轻松地创建层次结构的对象模型。
- 灵活的属性映射:JSONModel允许开发人员通过自定义属性映射规则来灵活处理属性与JSON键之间的映射关系。可以使用自定义的映射规则,使属性名和JSON键名之间存在不一致,从而适应不同的数据命名约定。
- 运行时特性支持:JSONModel使用运行时特性来动态处理属性。它使用关联对象(Associated Objects)来附加和获取额外的属性。这样可以在运行时动态地管理和访问属性,而无需修改原始类的定义。
- 扩展性和定制性:JSONModel提供了丰富的扩展点和定制选项,允许开发人员根据自己的需求进行定制和扩展。可以通过自定义转换器、忽略属性、替代属性名等方式来定制数据映射的行为,以满足各种复杂的数据处理需求。