MJExtension是一款开源的,简单易用的字典与模型转换框架。
常用的方法,主要是以下几个:
// JSON|字典 转 模型
+ (instancetype)mj_objectWithKeyValues:(id)keyValues;
// 通过 JSON|字典 为 模型赋值
- (instancetype)mj_setKeyValues:(id)keyValues;
// 模型转JSON
- (NSMutableDictionary *)mj_keyValues;
// JSON数组转模型数组
+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray;
功能
JSON|字典 转 模型
+ mj_objectWithKeyValues:
+ mj_objectWithKeyValues:
是框架中最简单的JSON转模型的方法,通过直接调用类方法并传入JSON数据即可快速实现转换。而在+ mj_objectWithKeyValues:
方法中,实际是调用了+ mj_objectWithKeyValues: context:
方法,参数中如果传了contenxt
,最终会返回CoreData模型;如果不传,返回已赋值的模型。
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 获得JSON对象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues参数不是一个字典");
// 判断是否传入 "contenxt" 参数
if ([self isSubclassOfClass:[NSManagedObject class]] && context) {
NSString *entityName = [NSStringFromClass(self) componentsSeparatedByString:@"."].lastObject;
return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context];
}
return [[[self alloc] init] mj_setKeyValues:keyValues];
}
在为实例赋值的方法中,由于- mj_setKeyValues:
实际的实现是调用- mj_setKeyValues: context:
。所以我们直接进入- mj_setKeyValues: context:
进行分析。
首先,需要将传入的keyValues
处理成可用的JSON对象,并获取当前类的类型,以及黑白名单属性。
// 获得JSON对象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典");
Class clazz = [self class];
NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
紧接着,调用类的扩展方法+ mj_enumerateProperties:
,获取和遍历类的属性列表,通过block参数进行回调,在回调的代码块中,对每个属性进行注意赋值。
核心代码:
//通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
@try {
// 0.检测是否被忽略
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
if ([ignoredPropertyNames containsObject:property.name]) return;
// 1.取出属性值
id value;
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
value = keyValues;
for (MJPropertyKey *propertyKey in propertyKeys) {
value = [propertyKey valueInObject:value];
}
if (value) break;
}
// 值的过滤
id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
if (newValue != value) { // 有过滤后的新值
[property setValue:newValue forObject:self];
return;
}
// 如果没有值,就直接返回
if (!value || value == [NSNull null]) return;
// 2.复杂处理
MJPropertyType *type = property.type; // 数据类型类
Class propertyClass = type.typeClass; // 对象类型
Class objectClass = [property objectClassInArrayForClass:[self class]]; // 数组中的模型类型
// 不可变 -> 可变处理
if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
value = [NSMutableArray arrayWithArray:value];
} else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
value = [NSMutableDictionary dictionaryWithDictionary:value];
} else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
value = [NSMutableString stringWithString:value];
} else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
value = [NSMutableData dataWithData:value];
}
if (!type.isFromFoundation && propertyClass) { // 模型属性
// 既不是基础类型,也不是NS类型。即:基本数据类型
value = [propertyClass mj_objectWithKeyValues:value context:context];
} else if (objectClass) {
if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
// string array -> url array
NSMutableArray *urlArray = [NSMutableArray array];
for (NSString *string in value) {
if (![string isKindOfClass:[NSString class]]) continue;
[urlArray addObject:string.mj_url];
}
value = urlArray;
} else { // 字典数组-->模型数组
value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
}
} else if (propertyClass == [NSString class]) {
if ([value isKindOfClass:[NSNumber class]]) {
// NSNumber -> NSString
value = [value description];
} else if ([value isKindOfClass:[NSURL class]]) {
// NSURL -> NSString
value = [value absoluteString];
}
} else if ([value isKindOfClass:[NSString class]]) {
if (propertyClass == [NSURL class]) {
// NSString -> NSURL
// 字符串转码
value = [value mj_url];
} else if (type.isNumberType) {
NSString *oldValue = value;
// NSString -> NSDecimalNumber, 使用 DecimalNumber 来转换数字, 避免丢失精度以及溢出
NSDecimalNumber *decimalValue = [NSDecimalNumber decimalNumberWithString:oldValue
locale:numberLocale];
// 检查特殊情况
if (decimalValue == NSDecimalNumber.notANumber) {
value = @(0);
}else if (propertyClass != [NSDecimalNumber class]) {
value = [decimalValue mj_standardValueWithTypeCode:type.code];
} else {
value = decimalValue;
}
// 如果是BOOL
if (type.isBoolType) {
// 字符串转BOOL(字符串没有charValue方法)
// 系统会调用字符串的charValue转为BOOL类型
NSString *lower = [oldValue lowercaseString];
if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
value = @YES;
} else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
value = @NO;
}
}
}
} else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
// 过滤 NSDecimalNumber类型
if (![value isKindOfClass:[NSDecimalNumber class]]) {
value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
}
}
// 经过转换后, 最终检查 value 与 property 是否匹配
if (propertyClass && ![value isKindOfClass:propertyClass]) {
value = nil;
}
// 3.赋值(KVC)
[property setValue:value forObject:self];
} @catch (NSException *exception) {
MJExtensionBuildError([self class], exception.reason);
MJExtensionLog(@"%@", exception);
}
}];
从代码中,可以直观看出,赋值操作主要分为步骤4个步骤。
0.检测是否被忽略
// 白名单
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
// 黑名单
if ([ignoredPropertyNames containsObject:property.name]) return;
判断黑白名单中是否包含相应的属性名称。
1.取出属性值
id value;
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
value = keyValues;
for (MJPropertyKey *propertyKey in propertyKeys) {
value = [propertyKey valueInObject:value];
}
if (value) break;
}
// 值的过滤
id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
if (newValue != value) { // 有过滤后的新值
[property setValue:newValue forObject:self];
return;
}
// 如果没有值,就直接返回
if (!value || value == [NSNull null]) return;
因为同一个成员属性,父类和子类的行为可能不一致(originKey、propertyKeys、objectClassInArray),所以其键值可能是一个数组,通过循环这个数组尝试获取值。
对值得过滤,指的是使用者通过实现- (id)mj_newValueFromOldValue: property:
方法,对结果进行进一步的处理(比如字符串日期处理为NSDate、字符串nil处理为@"")。
2.复杂处理
MJPropertyType *type = property.type; // 数据类型的信息
Class propertyClass = type.typeClass; // 属性的类型
Class objectClass = [property objectClassInArrayForClass:[self class]]; // 数组中模型的类型
// 不可变 -> 可变处理
if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
value = [NSMutableArray arrayWithArray:value];
} else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
value = [NSMutableDictionary dictionaryWithDictionary:value];
} else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
value = [NSMutableString stringWithString:value];
} else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
value = [NSMutableData dataWithData:value];
}
if (!type.isFromFoundation && propertyClass) { // 模型属性
// 既不是基础类型,也不是NS类型。即:基本数据类型
value = [propertyClass mj_objectWithKeyValues:value context:context];
} else if (objectClass) {
if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
// string array -> url array
NSMutableArray *urlArray = [NSMutableArray array];
for (NSString *string in value) {
if (![string isKindOfClass:[NSString class]]) continue;
[urlArray addObject:string.mj_url];
}
value = urlArray;
} else { // 字典数组-->模型数组
value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
}
} else if (propertyClass == [NSString class]) {
} else if ([value isKindOfClass:[NSString class]]) {
} else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
}
复杂处理,主要是对属性值的类型进行判断,属性值的类型只要分为:模型属性(自定义类)、数组属性和其他属性(NS类型)。
模型属性的value,需要通过继续调用- mj_objectWithKeyValues:value context:
方法,将字典转换成模型。
数组属性的值,则需要根据数组中模型的类型,进行循环转换。
其他情况的值,可以通过简单的转化或者直接使用。
3.赋值
至此,属性信息和值都有了。
// 经过转换后, 最终检查 value 与 property 是否匹配
if (propertyClass && ![value isKindOfClass:propertyClass]) {
value = nil;
}
[property setValue:value forObject:self];
在确定 value的值 与 property得类型 确实匹配后,通过KVC进行赋值。
/**
* 设置成员变量的值
*/
- (void)setValue:(id)value forObject:(id)object
{
if (self.type.KVCDisabled || value == nil) return;
[object setValue:value forKey:self.name];
}
到此,JSON转模型的工作就完成了。
模型 转 JSON|字典
- mj_keyValues
模型转JSON的方法主要有:
// 转换并返回模型中所有属性的键值对
- (NSMutableDictionary *)mj_keyValues;
/**
@para keys 需要返回的特定键的数组
@return 特定关键词的键值对
*/
- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys;
/**
@para ignoredKeys 需要忽略的特定键的数组
@return 除特定关键词的其他有效键值对
*/
- (NSMutableDictionary *)mj_keyValuesWithIgnoredKeys:(NSArray *)ignoredKeys;
以上方法统一调用了- mj_keyValuesWithKeys:ignoredKeys:
,让我们直接进入这个方法一探究竟。
- mj_keyValuesWithKeys:ignoredKeys:
方法与JSON转模型的核心逻辑是极其相似的,即通过遍历类的所有属性,进行相关操作,这里我们直接进入代码块,进行分析。
0.检测是否被忽略
// 白名单
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
// 黑名单
if ([ignoredPropertyNames containsObject:property.name]) return;
// 只需要返回的特定键
if (keys.count && ![keys containsObject:property.name]) return;
// 需要被忽略的特定键
if ([ignoredKeys containsObject:property.name]) return;
返回的结果,不仅可以对黑白名单中的属性进行筛选,还可以根据具体场景设置需要返回和忽略的特定键值。
1.取出属性值
使用KVC取值。
id value = [property valueForObject:self];
/**
* 获得成员变量的值
*/
- (id)valueForObject:(id)object
{
if (self.type.KVCDisabled) return [NSNull null];
id value = [object valueForKey:self.name];
// 32位BOOL类型转换json后成Int类型
/** https://github.com/CoderMJLee/MJExtension/issues/545 */
// 32 bit device OR 32 bit Simulator
#if defined(__arm__) || (TARGET_OS_SIMULATOR && !__LP64__)
if (self.type.isBoolType) {
value = @([(NSNumber *)value boolValue]);
}
#endif
return value;
}
2.模型属性和数组的处理
如果当前的属性属于模型类型或数组,则需要对 value
进行递归调用 - mj_keyValues
方法,直至最终得到非模型和非数组的数据类型。
MJPropertyType *type = property.type;
Class propertyClass = type.typeClass;
if (!type.isFromFoundation && propertyClass) {
value = [value mj_keyValues];
} else if ([value isKindOfClass:[NSArray class]]) {
// 3.处理数组里面有模型的情况
value = [NSObject mj_keyValuesArrayWithObjectArray:value];
} else if (propertyClass == [NSURL class]) {
value = [value absoluteString];
}
3.赋值
在对结果keyValues
进行赋值之前,需要先判断创建键值时,是否引用了替换键 —— 也就是在+ mj_replacedKeyFromPropertyName
方法中返回的自定义映射表。
对于没有引用替换键的值,可以直接赋值。
keyValues[property.name] = value;
对于引用了替换键的值,需要获取原始的key,最终结果也将返回最原始的JSON或字典。
// 获取原始key
NSArray *propertyKeys = [[property propertyKeysForClass:clazz] firstObject];
NSUInteger keyCount = propertyKeys.count;
// 创建字典
__block id innerContainer = keyValues;
[propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) {
// 下一个属性
MJPropertyKey *nextPropertyKey = nil;
if (idx != keyCount - 1) {
nextPropertyKey = propertyKeys[idx + 1];
}
if (nextPropertyKey) { // 不是最后一个key
// 当前propertyKey对应的字典或者数组
id tempInnerContainer = [propertyKey valueInObject:innerContainer];
if (tempInnerContainer == nil || [tempInnerContainer isKindOfClass:[NSNull class]]) {
if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) {
tempInnerContainer = [NSMutableDictionary dictionary];
} else {
tempInnerContainer = [NSMutableArray array];
}
if (propertyKey.type == MJPropertyKeyTypeDictionary) {
innerContainer[propertyKey.name] = tempInnerContainer;
} else {
innerContainer[propertyKey.name.intValue] = tempInnerContainer;
}
}
if ([tempInnerContainer isKindOfClass:[NSMutableArray class]]) {
NSMutableArray *tempInnerContainerArray = tempInnerContainer;
int index = nextPropertyKey.name.intValue;
while (tempInnerContainerArray.count < index + 1) {
[tempInnerContainerArray addObject:[NSNull null]];
}
}
innerContainer = tempInnerContainer;
} else { // 最后一个key
if (propertyKey.type == MJPropertyKeyTypeDictionary) {
innerContainer[propertyKey.name] = value;
} else {
innerContainer[propertyKey.name.intValue] = value;
}
}
}];
总结
核心代码:
- JSON|字典转模型的各类方法,最终都会调用
- mj_setKeyValues:(id)keyValues context:
- 模型转JSON|字典的各类方法,最终都会调用
- (NSMutableDictionary *)mj_keyValuesWithKeys: ignoredKeys:
性能方面:
- 使用runtime动态生成类的属性信息,并通过缓存机制进行性能提优。
容错方面
- 在JSON|字典转模型最后赋值之前,会对值和属性的类型进行一致性的判断。如果不匹配,
value
会被置为nil,避免潜在的Crash风险。
网友评论