NSObject+MJKeyValue.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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)mj_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 mj_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. #ifdef DEBUG
  196. [exception raise];
  197. #endif
  198. }
  199. }];
  200. // 转换完毕
  201. if ([self respondsToSelector:@selector(mj_didConvertToObjectWithKeyValues:)]) {
  202. [self mj_didConvertToObjectWithKeyValues:keyValues];
  203. }
  204. #pragma clang diagnostic push
  205. #pragma clang diagnostic ignored"-Wdeprecated-declarations"
  206. if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
  207. [self mj_keyValuesDidFinishConvertingToObject];
  208. }
  209. if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]) {
  210. [self mj_keyValuesDidFinishConvertingToObject:keyValues];
  211. }
  212. #pragma clang diagnostic pop
  213. return self;
  214. }
  215. + (instancetype)mj_objectWithKeyValues:(id)keyValues
  216. {
  217. return [self mj_objectWithKeyValues:keyValues context:nil];
  218. }
  219. + (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
  220. {
  221. // 获得JSON对象
  222. keyValues = [keyValues mj_JSONObject];
  223. MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues参数不是一个字典");
  224. if ([self isSubclassOfClass:[NSManagedObject class]] && context) {
  225. NSString *entityName = [(NSManagedObject *)self entity].name;
  226. return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context];
  227. }
  228. return [[[self alloc] init] mj_setKeyValues:keyValues];
  229. }
  230. + (instancetype)mj_objectWithFilename:(NSString *)filename
  231. {
  232. MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil");
  233. return [self mj_objectWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]];
  234. }
  235. + (instancetype)mj_objectWithFile:(NSString *)file
  236. {
  237. MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil");
  238. return [self mj_objectWithKeyValues:[NSDictionary dictionaryWithContentsOfFile:file]];
  239. }
  240. #pragma mark - 字典数组 -> 模型数组
  241. + (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(NSArray *)keyValuesArray
  242. {
  243. return [self mj_objectArrayWithKeyValuesArray:keyValuesArray context:nil];
  244. }
  245. + (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context
  246. {
  247. // 如果是JSON字符串
  248. keyValuesArray = [keyValuesArray mj_JSONObject];
  249. // 1.判断真实性
  250. MJExtensionAssertError([keyValuesArray isKindOfClass:[NSArray class]], nil, [self class], @"keyValuesArray参数不是一个数组");
  251. // 如果数组里面放的是NSString、NSNumber等数据
  252. if ([MJFoundation isClassFromFoundation:self]) return [NSMutableArray arrayWithArray:keyValuesArray];
  253. // 2.创建数组
  254. NSMutableArray *modelArray = [NSMutableArray array];
  255. // 3.遍历
  256. for (NSDictionary *keyValues in keyValuesArray) {
  257. if ([keyValues isKindOfClass:[NSArray class]]){
  258. [modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]];
  259. } else {
  260. id model = [self mj_objectWithKeyValues:keyValues context:context];
  261. if (model) [modelArray addObject:model];
  262. }
  263. }
  264. return modelArray;
  265. }
  266. + (NSMutableArray *)mj_objectArrayWithFilename:(NSString *)filename
  267. {
  268. MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil");
  269. return [self mj_objectArrayWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]];
  270. }
  271. + (NSMutableArray *)mj_objectArrayWithFile:(NSString *)file
  272. {
  273. MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil");
  274. return [self mj_objectArrayWithKeyValuesArray:[NSArray arrayWithContentsOfFile:file]];
  275. }
  276. #pragma mark - 模型 -> 字典
  277. - (NSMutableDictionary *)mj_keyValues
  278. {
  279. return [self mj_keyValuesWithKeys:nil ignoredKeys:nil];
  280. }
  281. - (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys
  282. {
  283. return [self mj_keyValuesWithKeys:keys ignoredKeys:nil];
  284. }
  285. - (NSMutableDictionary *)mj_keyValuesWithIgnoredKeys:(NSArray *)ignoredKeys
  286. {
  287. return [self mj_keyValuesWithKeys:nil ignoredKeys:ignoredKeys];
  288. }
  289. - (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
  290. {
  291. // 如果自己不是模型类, 那就返回自己
  292. // 模型类过滤掉 NSNull
  293. // 唯一一个不返回自己的
  294. if ([self isMemberOfClass:NSNull.class]) { return nil; }
  295. // 这里虽然返回了自己, 但是其实是有报错信息的.
  296. // TODO: 报错机制不好, 需要重做
  297. MJExtensionAssertError(![MJFoundation isClassFromFoundation:[self class]], (NSMutableDictionary *)self, [self class], @"不是自定义的模型类")
  298. id keyValues = [NSMutableDictionary dictionary];
  299. Class clazz = [self class];
  300. NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
  301. NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
  302. [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
  303. @try {
  304. // 0.检测是否被忽略
  305. if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
  306. if ([ignoredPropertyNames containsObject:property.name]) return;
  307. if (keys.count && ![keys containsObject:property.name]) return;
  308. if ([ignoredKeys containsObject:property.name]) return;
  309. // 1.取出属性值
  310. id value = [property valueForObject:self];
  311. if (!value) return;
  312. // 2.如果是模型属性
  313. MJPropertyType *type = property.type;
  314. Class propertyClass = type.typeClass;
  315. if (!type.isFromFoundation && propertyClass) {
  316. value = [value mj_keyValues];
  317. } else if ([value isKindOfClass:[NSArray class]]) {
  318. // 3.处理数组里面有模型的情况
  319. value = [NSObject mj_keyValuesArrayWithObjectArray:value];
  320. } else if (propertyClass == [NSURL class]) {
  321. value = [value absoluteString];
  322. }
  323. // 4.赋值
  324. if ([clazz mj_isReferenceReplacedKeyWhenCreatingKeyValues]) {
  325. NSArray *propertyKeys = [[property propertyKeysForClass:clazz] firstObject];
  326. NSUInteger keyCount = propertyKeys.count;
  327. // 创建字典
  328. __block id innerContainer = keyValues;
  329. [propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) {
  330. // 下一个属性
  331. MJPropertyKey *nextPropertyKey = nil;
  332. if (idx != keyCount - 1) {
  333. nextPropertyKey = propertyKeys[idx + 1];
  334. }
  335. if (nextPropertyKey) { // 不是最后一个key
  336. // 当前propertyKey对应的字典或者数组
  337. id tempInnerContainer = [propertyKey valueInObject:innerContainer];
  338. if (tempInnerContainer == nil || [tempInnerContainer isKindOfClass:[NSNull class]]) {
  339. if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) {
  340. tempInnerContainer = [NSMutableDictionary dictionary];
  341. } else {
  342. tempInnerContainer = [NSMutableArray array];
  343. }
  344. if (propertyKey.type == MJPropertyKeyTypeDictionary) {
  345. innerContainer[propertyKey.name] = tempInnerContainer;
  346. } else {
  347. innerContainer[propertyKey.name.intValue] = tempInnerContainer;
  348. }
  349. }
  350. if ([tempInnerContainer isKindOfClass:[NSMutableArray class]]) {
  351. NSMutableArray *tempInnerContainerArray = tempInnerContainer;
  352. int index = nextPropertyKey.name.intValue;
  353. while (tempInnerContainerArray.count < index + 1) {
  354. [tempInnerContainerArray addObject:[NSNull null]];
  355. }
  356. }
  357. innerContainer = tempInnerContainer;
  358. } else { // 最后一个key
  359. if (propertyKey.type == MJPropertyKeyTypeDictionary) {
  360. innerContainer[propertyKey.name] = value;
  361. } else {
  362. innerContainer[propertyKey.name.intValue] = value;
  363. }
  364. }
  365. }];
  366. } else {
  367. keyValues[property.name] = value;
  368. }
  369. } @catch (NSException *exception) {
  370. MJExtensionBuildError([self class], exception.reason);
  371. MJExtensionLog(@"%@", exception);
  372. #ifdef DEBUG
  373. [exception raise];
  374. #endif
  375. }
  376. }];
  377. // 转换完毕
  378. if ([self respondsToSelector:@selector(mj_objectDidConvertToKeyValues:)]) {
  379. [self mj_objectDidConvertToKeyValues:keyValues];
  380. }
  381. #pragma clang diagnostic push
  382. #pragma clang diagnostic ignored"-Wdeprecated-declarations"
  383. if ([self respondsToSelector:@selector(mj_objectDidFinishConvertingToKeyValues)]) {
  384. [self mj_objectDidFinishConvertingToKeyValues];
  385. }
  386. #pragma clang diagnostic pop
  387. return keyValues;
  388. }
  389. #pragma mark - 模型数组 -> 字典数组
  390. + (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray
  391. {
  392. return [self mj_keyValuesArrayWithObjectArray:objectArray keys:nil ignoredKeys:nil];
  393. }
  394. + (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys
  395. {
  396. return [self mj_keyValuesArrayWithObjectArray:objectArray keys:keys ignoredKeys:nil];
  397. }
  398. + (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray ignoredKeys:(NSArray *)ignoredKeys
  399. {
  400. return [self mj_keyValuesArrayWithObjectArray:objectArray keys:nil ignoredKeys:ignoredKeys];
  401. }
  402. + (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
  403. {
  404. // 0.判断真实性
  405. MJExtensionAssertError([objectArray isKindOfClass:[NSArray class]], nil, [self class], @"objectArray参数不是一个数组");
  406. // 1.创建数组
  407. NSMutableArray *keyValuesArray = [NSMutableArray array];
  408. for (id object in objectArray) {
  409. if (keys) {
  410. id convertedObj = [object mj_keyValuesWithKeys:keys];
  411. if (!convertedObj) { continue; }
  412. [keyValuesArray addObject:convertedObj];
  413. } else {
  414. id convertedObj = [object mj_keyValuesWithIgnoredKeys:ignoredKeys];
  415. if (!convertedObj) { continue; }
  416. [keyValuesArray addObject:convertedObj];
  417. }
  418. }
  419. return keyValuesArray;
  420. }
  421. #pragma mark - 转换为JSON
  422. - (NSData *)mj_JSONData
  423. {
  424. if ([self isKindOfClass:[NSString class]]) {
  425. return [((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding];
  426. } else if ([self isKindOfClass:[NSData class]]) {
  427. return (NSData *)self;
  428. }
  429. return [NSJSONSerialization dataWithJSONObject:[self mj_JSONObject] options:kNilOptions error:nil];
  430. }
  431. - (id)mj_JSONObject
  432. {
  433. if ([self isKindOfClass:[NSString class]]) {
  434. return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
  435. } else if ([self isKindOfClass:[NSData class]]) {
  436. return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
  437. }
  438. return self.mj_keyValues;
  439. }
  440. - (NSString *)mj_JSONString
  441. {
  442. if ([self isKindOfClass:[NSString class]]) {
  443. return (NSString *)self;
  444. } else if ([self isKindOfClass:[NSData class]]) {
  445. return [[NSString alloc] initWithData:(NSData *)self encoding:NSUTF8StringEncoding];
  446. }
  447. return [[NSString alloc] initWithData:[self mj_JSONData] encoding:NSUTF8StringEncoding];
  448. }
  449. @end