NSObject+MJKeyValue.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. //
  2. // NSObject+MJKeyValue.m
  3. // MJExtension
  4. //
  5. // Created by mj on 13-8-24.
  6. // Copyright (c) 2013年 小码哥. All rights reserved.
  7. //
  8. #import "NSObject+MJKeyValue.h"
  9. #import "NSObject+MJProperty.h"
  10. #import "NSString+MJExtension.h"
  11. #import "MJProperty.h"
  12. #import "MJPropertyType.h"
  13. #import "MJExtensionConst.h"
  14. #import "MJFoundation.h"
  15. #import "NSString+MJExtension.h"
  16. #import "NSObject+MJClass.h"
  17. @implementation NSDecimalNumber(MJKeyValue)
  18. - (id)standardValueWithTypeCode:(NSString *)typeCode {
  19. // 由于这里涉及到编译器问题, 暂时保留 Long, 实际上在 64 位系统上, 这 2 个精度范围相同,
  20. // 32 位略有不同, 其余都可使用 Double 进行强转不丢失精度
  21. if ([typeCode isEqualToString:MJPropertyTypeLongLong]) {
  22. return @(self.longLongValue);
  23. } else if ([typeCode isEqualToString:MJPropertyTypeLongLong.uppercaseString]) {
  24. return @(self.unsignedLongLongValue);
  25. } else if ([typeCode isEqualToString:MJPropertyTypeLong]) {
  26. return @(self.longValue);
  27. } else if ([typeCode isEqualToString:MJPropertyTypeLong.uppercaseString]) {
  28. return @(self.unsignedLongValue);
  29. } else {
  30. return @(self.doubleValue);
  31. }
  32. }
  33. @end
  34. @implementation NSObject (MJKeyValue)
  35. #pragma mark - 错误
  36. static const char MJErrorKey = '\0';
  37. + (NSError *)mj_error
  38. {
  39. return objc_getAssociatedObject(self, &MJErrorKey);
  40. }
  41. + (void)setMj_error:(NSError *)error
  42. {
  43. objc_setAssociatedObject(self, &MJErrorKey, error, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  44. }
  45. #pragma mark - 模型 -> 字典时的参考
  46. /** 模型转字典时,字典的key是否参考replacedKeyFromPropertyName等方法(父类设置了,子类也会继承下来) */
  47. static const char MJReferenceReplacedKeyWhenCreatingKeyValuesKey = '\0';
  48. + (void)mj_referenceReplacedKeyWhenCreatingKeyValues:(BOOL)reference
  49. {
  50. objc_setAssociatedObject(self, &MJReferenceReplacedKeyWhenCreatingKeyValuesKey, @(reference), OBJC_ASSOCIATION_ASSIGN);
  51. }
  52. + (BOOL)mj_isReferenceReplacedKeyWhenCreatingKeyValues
  53. {
  54. __block id value = objc_getAssociatedObject(self, &MJReferenceReplacedKeyWhenCreatingKeyValuesKey);
  55. if (!value) {
  56. [self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
  57. value = objc_getAssociatedObject(c, &MJReferenceReplacedKeyWhenCreatingKeyValuesKey);
  58. if (value) *stop = YES;
  59. }];
  60. }
  61. return [value boolValue];
  62. }
  63. #pragma mark - --常用的对象--
  64. + (void)load
  65. {
  66. // 默认设置
  67. [self mj_referenceReplacedKeyWhenCreatingKeyValues:YES];
  68. }
  69. #pragma mark - --公共方法--
  70. #pragma mark - 字典 -> 模型
  71. - (instancetype)mj_setKeyValues:(id)keyValues
  72. {
  73. return [self mj_setKeyValues:keyValues context:nil];
  74. }
  75. /**
  76. 核心代码:
  77. */
  78. - (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
  79. {
  80. // 获得JSON对象
  81. keyValues = [keyValues mj_JSONObject];
  82. MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典");
  83. Class clazz = [self class];
  84. NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
  85. NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
  86. NSLocale *numberLocale = nil;
  87. if ([self.class respondsToSelector:@selector(mj_numberLocale)]) {
  88. numberLocale = self.class.mj_numberLocale;
  89. }
  90. //通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
  91. [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
  92. @try {
  93. // 0.检测是否被忽略
  94. if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
  95. if ([ignoredPropertyNames containsObject:property.name]) return;
  96. // 1.取出属性值
  97. id value;
  98. NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
  99. for (NSArray *propertyKeys in propertyKeyses) {
  100. value = keyValues;
  101. for (MJPropertyKey *propertyKey in propertyKeys) {
  102. value = [propertyKey valueInObject:value];
  103. }
  104. if (value) break;
  105. }
  106. // 值的过滤
  107. id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
  108. if (newValue != value) { // 有过滤后的新值
  109. [property setValue:newValue forObject:self];
  110. return;
  111. }
  112. // 如果没有值,就直接返回
  113. if (!value || value == [NSNull null]) return;
  114. // 2.复杂处理
  115. MJPropertyType *type = property.type;
  116. Class propertyClass = type.typeClass;
  117. Class objectClass = [property objectClassInArrayForClass:[self class]];
  118. // 不可变 -> 可变处理
  119. if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
  120. value = [NSMutableArray arrayWithArray:value];
  121. } else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
  122. value = [NSMutableDictionary dictionaryWithDictionary:value];
  123. } else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
  124. value = [NSMutableString stringWithString:value];
  125. } else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
  126. value = [NSMutableData dataWithData:value];
  127. }
  128. if (!type.isFromFoundation && propertyClass) { // 模型属性
  129. value = [propertyClass mj_objectWithKeyValues:value context:context];
  130. } else if (objectClass) {
  131. if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
  132. // string array -> url array
  133. NSMutableArray *urlArray = [NSMutableArray array];
  134. for (NSString *string in value) {
  135. if (![string isKindOfClass:[NSString class]]) continue;
  136. [urlArray addObject:string.mj_url];
  137. }
  138. value = urlArray;
  139. } else { // 字典数组-->模型数组
  140. value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
  141. }
  142. } else if (propertyClass == [NSString class]) {
  143. if ([value isKindOfClass:[NSNumber class]]) {
  144. // NSNumber -> NSString
  145. value = [value description];
  146. } else if ([value isKindOfClass:[NSURL class]]) {
  147. // NSURL -> NSString
  148. value = [value absoluteString];
  149. }
  150. } else if ([value isKindOfClass:[NSString class]]) {
  151. if (propertyClass == [NSURL class]) {
  152. // NSString -> NSURL
  153. // 字符串转码
  154. value = [value mj_url];
  155. } else if (type.isNumberType) {
  156. NSString *oldValue = value;
  157. // NSString -> NSDecimalNumber, 使用 DecimalNumber 来转换数字, 避免丢失精度以及溢出
  158. NSDecimalNumber *decimalValue = [NSDecimalNumber decimalNumberWithString:oldValue
  159. locale:numberLocale];
  160. // 检查特殊情况
  161. if (decimalValue == NSDecimalNumber.notANumber) {
  162. value = @(0);
  163. }else if (propertyClass != [NSDecimalNumber class]) {
  164. value = [decimalValue standardValueWithTypeCode:type.code];
  165. } else {
  166. value = decimalValue;
  167. }
  168. // 如果是BOOL
  169. if (type.isBoolType) {
  170. // 字符串转BOOL(字符串没有charValue方法)
  171. // 系统会调用字符串的charValue转为BOOL类型
  172. NSString *lower = [oldValue lowercaseString];
  173. if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
  174. value = @YES;
  175. } else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
  176. value = @NO;
  177. }
  178. }
  179. }
  180. } else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
  181. // 过滤 NSDecimalNumber类型
  182. if (![value isKindOfClass:[NSDecimalNumber class]]) {
  183. value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
  184. }
  185. }
  186. // 经过转换后, 最终检查 value 与 property 是否匹配
  187. if (propertyClass && ![value isKindOfClass:propertyClass]) {
  188. value = nil;
  189. }
  190. // 3.赋值
  191. [property setValue:value forObject:self];
  192. } @catch (NSException *exception) {
  193. MJExtensionBuildError([self class], exception.reason);
  194. MJExtensionLog(@"%@", exception);
  195. }
  196. }];
  197. // 转换完毕
  198. if ([self respondsToSelector:@selector(mj_didConvertToObjectWithKeyValues:)]) {
  199. [self mj_didConvertToObjectWithKeyValues:keyValues];
  200. }
  201. #pragma clang diagnostic push
  202. #pragma clang diagnostic ignored"-Wdeprecated-declarations"
  203. if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
  204. [self mj_keyValuesDidFinishConvertingToObject];
  205. }
  206. if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]) {
  207. [self mj_keyValuesDidFinishConvertingToObject:keyValues];
  208. }
  209. #pragma clang diagnostic pop
  210. return self;
  211. }
  212. + (instancetype)mj_objectWithKeyValues:(id)keyValues
  213. {
  214. return [self mj_objectWithKeyValues:keyValues context:nil];
  215. }
  216. + (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
  217. {
  218. // 获得JSON对象
  219. keyValues = [keyValues mj_JSONObject];
  220. MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues参数不是一个字典");
  221. if ([self isSubclassOfClass:[NSManagedObject class]] && context) {
  222. NSString *entityName = [NSStringFromClass(self) componentsSeparatedByString:@"."].lastObject;
  223. return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context];
  224. }
  225. return [[[self alloc] init] mj_setKeyValues:keyValues];
  226. }
  227. + (instancetype)mj_objectWithFilename:(NSString *)filename
  228. {
  229. MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil");
  230. return [self mj_objectWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]];
  231. }
  232. + (instancetype)mj_objectWithFile:(NSString *)file
  233. {
  234. MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil");
  235. return [self mj_objectWithKeyValues:[NSDictionary dictionaryWithContentsOfFile:file]];
  236. }
  237. #pragma mark - 字典数组 -> 模型数组
  238. + (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(NSArray *)keyValuesArray
  239. {
  240. return [self mj_objectArrayWithKeyValuesArray:keyValuesArray context:nil];
  241. }
  242. + (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context
  243. {
  244. // 如果是JSON字符串
  245. keyValuesArray = [keyValuesArray mj_JSONObject];
  246. // 1.判断真实性
  247. MJExtensionAssertError([keyValuesArray isKindOfClass:[NSArray class]], nil, [self class], @"keyValuesArray参数不是一个数组");
  248. // 如果数组里面放的是NSString、NSNumber等数据
  249. if ([MJFoundation isClassFromFoundation:self]) return [NSMutableArray arrayWithArray:keyValuesArray];
  250. // 2.创建数组
  251. NSMutableArray *modelArray = [NSMutableArray array];
  252. // 3.遍历
  253. for (NSDictionary *keyValues in keyValuesArray) {
  254. if ([keyValues isKindOfClass:[NSArray class]]){
  255. [modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]];
  256. } else {
  257. id model = [self mj_objectWithKeyValues:keyValues context:context];
  258. if (model) [modelArray addObject:model];
  259. }
  260. }
  261. return modelArray;
  262. }
  263. + (NSMutableArray *)mj_objectArrayWithFilename:(NSString *)filename
  264. {
  265. MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil");
  266. return [self mj_objectArrayWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]];
  267. }
  268. + (NSMutableArray *)mj_objectArrayWithFile:(NSString *)file
  269. {
  270. MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil");
  271. return [self mj_objectArrayWithKeyValuesArray:[NSArray arrayWithContentsOfFile:file]];
  272. }
  273. #pragma mark - 模型 -> 字典
  274. - (NSMutableDictionary *)mj_keyValues
  275. {
  276. return [self mj_keyValuesWithKeys:nil ignoredKeys:nil];
  277. }
  278. - (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys
  279. {
  280. return [self mj_keyValuesWithKeys:keys ignoredKeys:nil];
  281. }
  282. - (NSMutableDictionary *)mj_keyValuesWithIgnoredKeys:(NSArray *)ignoredKeys
  283. {
  284. return [self mj_keyValuesWithKeys:nil ignoredKeys:ignoredKeys];
  285. }
  286. - (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
  287. {
  288. // 如果自己不是模型类, 那就返回自己
  289. // 模型类过滤掉 NSNull
  290. // 唯一一个不返回自己的
  291. if ([self isMemberOfClass:NSNull.class]) { return nil; }
  292. // 这里虽然返回了自己, 但是其实是有报错信息的.
  293. // TODO: 报错机制不好, 需要重做
  294. MJExtensionAssertError(![MJFoundation isClassFromFoundation:[self class]], (NSMutableDictionary *)self, [self class], @"不是自定义的模型类")
  295. id keyValues = [NSMutableDictionary dictionary];
  296. Class clazz = [self class];
  297. NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
  298. NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
  299. [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
  300. @try {
  301. // 0.检测是否被忽略
  302. if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
  303. if ([ignoredPropertyNames containsObject:property.name]) return;
  304. if (keys.count && ![keys containsObject:property.name]) return;
  305. if ([ignoredKeys containsObject:property.name]) return;
  306. // 1.取出属性值
  307. id value = [property valueForObject:self];
  308. if (!value) return;
  309. // 2.如果是模型属性
  310. MJPropertyType *type = property.type;
  311. Class propertyClass = type.typeClass;
  312. if (!type.isFromFoundation && propertyClass) {
  313. value = [value mj_keyValues];
  314. } else if ([value isKindOfClass:[NSArray class]]) {
  315. // 3.处理数组里面有模型的情况
  316. value = [NSObject mj_keyValuesArrayWithObjectArray:value];
  317. } else if (propertyClass == [NSURL class]) {
  318. value = [value absoluteString];
  319. }
  320. // 4.赋值
  321. if ([clazz mj_isReferenceReplacedKeyWhenCreatingKeyValues]) {
  322. NSArray *propertyKeys = [[property propertyKeysForClass:clazz] firstObject];
  323. NSUInteger keyCount = propertyKeys.count;
  324. // 创建字典
  325. __block id innerContainer = keyValues;
  326. [propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) {
  327. // 下一个属性
  328. MJPropertyKey *nextPropertyKey = nil;
  329. if (idx != keyCount - 1) {
  330. nextPropertyKey = propertyKeys[idx + 1];
  331. }
  332. if (nextPropertyKey) { // 不是最后一个key
  333. // 当前propertyKey对应的字典或者数组
  334. id tempInnerContainer = [propertyKey valueInObject:innerContainer];
  335. if (tempInnerContainer == nil || [tempInnerContainer isKindOfClass:[NSNull class]]) {
  336. if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) {
  337. tempInnerContainer = [NSMutableDictionary dictionary];
  338. } else {
  339. tempInnerContainer = [NSMutableArray array];
  340. }
  341. if (propertyKey.type == MJPropertyKeyTypeDictionary) {
  342. innerContainer[propertyKey.name] = tempInnerContainer;
  343. } else {
  344. innerContainer[propertyKey.name.intValue] = tempInnerContainer;
  345. }
  346. }
  347. if ([tempInnerContainer isKindOfClass:[NSMutableArray class]]) {
  348. NSMutableArray *tempInnerContainerArray = tempInnerContainer;
  349. int index = nextPropertyKey.name.intValue;
  350. while (tempInnerContainerArray.count < index + 1) {
  351. [tempInnerContainerArray addObject:[NSNull null]];
  352. }
  353. }
  354. innerContainer = tempInnerContainer;
  355. } else { // 最后一个key
  356. if (propertyKey.type == MJPropertyKeyTypeDictionary) {
  357. innerContainer[propertyKey.name] = value;
  358. } else {
  359. innerContainer[propertyKey.name.intValue] = value;
  360. }
  361. }
  362. }];
  363. } else {
  364. keyValues[property.name] = value;
  365. }
  366. } @catch (NSException *exception) {
  367. MJExtensionBuildError([self class], exception.reason);
  368. MJExtensionLog(@"%@", exception);
  369. }
  370. }];
  371. // 转换完毕
  372. if ([self respondsToSelector:@selector(mj_objectDidConvertToKeyValues:)]) {
  373. [self mj_objectDidConvertToKeyValues:keyValues];
  374. }
  375. #pragma clang diagnostic push
  376. #pragma clang diagnostic ignored"-Wdeprecated-declarations"
  377. if ([self respondsToSelector:@selector(mj_objectDidFinishConvertingToKeyValues)]) {
  378. [self mj_objectDidFinishConvertingToKeyValues];
  379. }
  380. #pragma clang diagnostic pop
  381. return keyValues;
  382. }
  383. #pragma mark - 模型数组 -> 字典数组
  384. + (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray
  385. {
  386. return [self mj_keyValuesArrayWithObjectArray:objectArray keys:nil ignoredKeys:nil];
  387. }
  388. + (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys
  389. {
  390. return [self mj_keyValuesArrayWithObjectArray:objectArray keys:keys ignoredKeys:nil];
  391. }
  392. + (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray ignoredKeys:(NSArray *)ignoredKeys
  393. {
  394. return [self mj_keyValuesArrayWithObjectArray:objectArray keys:nil ignoredKeys:ignoredKeys];
  395. }
  396. + (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
  397. {
  398. // 0.判断真实性
  399. MJExtensionAssertError([objectArray isKindOfClass:[NSArray class]], nil, [self class], @"objectArray参数不是一个数组");
  400. // 1.创建数组
  401. NSMutableArray *keyValuesArray = [NSMutableArray array];
  402. for (id object in objectArray) {
  403. if (keys) {
  404. id convertedObj = [object mj_keyValuesWithKeys:keys];
  405. if (!convertedObj) { continue; }
  406. [keyValuesArray addObject:convertedObj];
  407. } else {
  408. id convertedObj = [object mj_keyValuesWithIgnoredKeys:ignoredKeys];
  409. if (!convertedObj) { continue; }
  410. [keyValuesArray addObject:convertedObj];
  411. }
  412. }
  413. return keyValuesArray;
  414. }
  415. #pragma mark - 转换为JSON
  416. - (NSData *)mj_JSONData
  417. {
  418. if ([self isKindOfClass:[NSString class]]) {
  419. return [((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding];
  420. } else if ([self isKindOfClass:[NSData class]]) {
  421. return (NSData *)self;
  422. }
  423. return [NSJSONSerialization dataWithJSONObject:[self mj_JSONObject] options:kNilOptions error:nil];
  424. }
  425. - (id)mj_JSONObject
  426. {
  427. if ([self isKindOfClass:[NSString class]]) {
  428. return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
  429. } else if ([self isKindOfClass:[NSData class]]) {
  430. return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
  431. }
  432. return self.mj_keyValues;
  433. }
  434. - (NSString *)mj_JSONString
  435. {
  436. if ([self isKindOfClass:[NSString class]]) {
  437. return (NSString *)self;
  438. } else if ([self isKindOfClass:[NSData class]]) {
  439. return [[NSString alloc] initWithData:(NSData *)self encoding:NSUTF8StringEncoding];
  440. }
  441. return [[NSString alloc] initWithData:[self mj_JSONData] encoding:NSUTF8StringEncoding];
  442. }
  443. @end