MTLJSONAdapter.m 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. //
  2. // MTLJSONAdapter.m
  3. // Mantle
  4. //
  5. // Created by Justin Spahr-Summers on 2013-02-12.
  6. // Copyright (c) 2013 GitHub. All rights reserved.
  7. //
  8. #import <objc/runtime.h>
  9. #import "NSDictionary+MTLJSONKeyPath.h"
  10. #import "EXTRuntimeExtensions.h"
  11. #import "EXTScope.h"
  12. #import "MTLJSONAdapter.h"
  13. #import "MTLModel.h"
  14. #import "MTLTransformerErrorHandling.h"
  15. #import "MTLReflection.h"
  16. #import "NSValueTransformer+MTLPredefinedTransformerAdditions.h"
  17. #import "MTLValueTransformer.h"
  18. NSString * const MTLJSONAdapterErrorDomain = @"MTLJSONAdapterErrorDomain";
  19. const NSInteger MTLJSONAdapterErrorNoClassFound = 2;
  20. const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary = 3;
  21. const NSInteger MTLJSONAdapterErrorInvalidJSONMapping = 4;
  22. // An exception was thrown and caught.
  23. const NSInteger MTLJSONAdapterErrorExceptionThrown = 1;
  24. // Associated with the NSException that was caught.
  25. static NSString * const MTLJSONAdapterThrownExceptionErrorKey = @"MTLJSONAdapterThrownException";
  26. @interface MTLJSONAdapter ()
  27. // The MTLModel subclass being parsed, or the class of `model` if parsing has
  28. // completed.
  29. @property (nonatomic, strong, readonly) Class modelClass;
  30. // A cached copy of the return value of +JSONKeyPathsByPropertyKey.
  31. @property (nonatomic, copy, readonly) NSDictionary *JSONKeyPathsByPropertyKey;
  32. // A cached copy of the return value of -valueTransformersForModelClass:
  33. @property (nonatomic, copy, readonly) NSDictionary *valueTransformersByPropertyKey;
  34. // Used to cache the JSON adapters returned by -JSONAdapterForModelClass:error:.
  35. @property (nonatomic, strong, readonly) NSMapTable *JSONAdaptersByModelClass;
  36. // If +classForParsingJSONDictionary: returns a model class different from the
  37. // one this adapter was initialized with, use this method to obtain a cached
  38. // instance of a suitable adapter instead.
  39. //
  40. // modelClass - The class from which to parse the JSON. This class must conform
  41. // to <MTLJSONSerializing>. This argument must not be nil.
  42. // error - If not NULL, this may be set to an error that occurs during
  43. // initializing the adapter.
  44. //
  45. // Returns a JSON adapter for modelClass, creating one of necessary. If no
  46. // adapter could be created, nil is returned.
  47. - (MTLJSONAdapter *)JSONAdapterForModelClass:(Class)modelClass error:(NSError **)error;
  48. // Collect all value transformers needed for a given class.
  49. //
  50. // modelClass - The class from which to parse the JSON. This class must conform
  51. // to <MTLJSONSerializing>. This argument must not be nil.
  52. //
  53. // Returns a dictionary with the properties of modelClass that need
  54. // transformation as keys and the value transformers as values.
  55. + (NSDictionary *)valueTransformersForModelClass:(Class)modelClass;
  56. @end
  57. @implementation MTLJSONAdapter
  58. #pragma mark Convenience methods
  59. + (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error {
  60. MTLJSONAdapter *adapter = [[self alloc] initWithModelClass:modelClass];
  61. return [adapter modelFromJSONDictionary:JSONDictionary error:error];
  62. }
  63. + (NSArray *)modelsOfClass:(Class)modelClass fromJSONArray:(NSArray *)JSONArray error:(NSError **)error {
  64. if (JSONArray == nil || ![JSONArray isKindOfClass:NSArray.class]) {
  65. if (error != NULL) {
  66. NSDictionary *userInfo = @{
  67. NSLocalizedDescriptionKey: NSLocalizedString(@"Missing JSON array", @""),
  68. NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"%@ could not be created because an invalid JSON array was provided: %@", @""), NSStringFromClass(modelClass), JSONArray.class],
  69. };
  70. *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorInvalidJSONDictionary userInfo:userInfo];
  71. }
  72. return nil;
  73. }
  74. NSMutableArray *models = [NSMutableArray arrayWithCapacity:JSONArray.count];
  75. for (NSDictionary *JSONDictionary in JSONArray){
  76. MTLModel *model = [self modelOfClass:modelClass fromJSONDictionary:JSONDictionary error:error];
  77. if (model == nil) return nil;
  78. [models addObject:model];
  79. }
  80. return models;
  81. }
  82. + (NSDictionary *)JSONDictionaryFromModel:(id<MTLJSONSerializing>)model error:(NSError **)error {
  83. MTLJSONAdapter *adapter = [[self alloc] initWithModelClass:model.class];
  84. return [adapter JSONDictionaryFromModel:model error:error];
  85. }
  86. + (NSArray *)JSONArrayFromModels:(NSArray *)models error:(NSError **)error {
  87. NSParameterAssert(models != nil);
  88. NSParameterAssert([models isKindOfClass:NSArray.class]);
  89. NSMutableArray *JSONArray = [NSMutableArray arrayWithCapacity:models.count];
  90. for (MTLModel<MTLJSONSerializing> *model in models) {
  91. NSDictionary *JSONDictionary = [self JSONDictionaryFromModel:model error:error];
  92. if (JSONDictionary == nil) return nil;
  93. [JSONArray addObject:JSONDictionary];
  94. }
  95. return JSONArray;
  96. }
  97. #pragma mark Lifecycle
  98. - (id)init {
  99. NSAssert(NO, @"%@ must be initialized with a model class", self.class);
  100. return nil;
  101. }
  102. - (id)initWithModelClass:(Class)modelClass {
  103. NSParameterAssert(modelClass != nil);
  104. NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);
  105. self = [super init];
  106. if (self == nil) return nil;
  107. _modelClass = modelClass;
  108. _JSONKeyPathsByPropertyKey = [modelClass JSONKeyPathsByPropertyKey];
  109. NSSet *propertyKeys = [self.modelClass propertyKeys];
  110. for (NSString *mappedPropertyKey in _JSONKeyPathsByPropertyKey) {
  111. if (![propertyKeys containsObject:mappedPropertyKey]) {
  112. NSAssert(NO, @"%@ is not a property of %@.", mappedPropertyKey, modelClass);
  113. return nil;
  114. }
  115. id value = _JSONKeyPathsByPropertyKey[mappedPropertyKey];
  116. if ([value isKindOfClass:NSArray.class]) {
  117. for (NSString *keyPath in value) {
  118. if ([keyPath isKindOfClass:NSString.class]) continue;
  119. NSAssert(NO, @"%@ must either map to a JSON key path or a JSON array of key paths, got: %@.", mappedPropertyKey, value);
  120. return nil;
  121. }
  122. } else if (![value isKindOfClass:NSString.class]) {
  123. NSAssert(NO, @"%@ must either map to a JSON key path or a JSON array of key paths, got: %@.",mappedPropertyKey, value);
  124. return nil;
  125. }
  126. }
  127. _valueTransformersByPropertyKey = [self.class valueTransformersForModelClass:modelClass];
  128. _JSONAdaptersByModelClass = [NSMapTable strongToStrongObjectsMapTable];
  129. return self;
  130. }
  131. #pragma mark Serialization
  132. - (NSDictionary *)JSONDictionaryFromModel:(id<MTLJSONSerializing>)model error:(NSError **)error {
  133. NSParameterAssert(model != nil);
  134. NSParameterAssert([model isKindOfClass:self.modelClass]);
  135. if (self.modelClass != model.class) {
  136. MTLJSONAdapter *otherAdapter = [self JSONAdapterForModelClass:model.class error:error];
  137. return [otherAdapter JSONDictionaryFromModel:model error:error];
  138. }
  139. NSSet *propertyKeysToSerialize = [self serializablePropertyKeys:[NSSet setWithArray:self.JSONKeyPathsByPropertyKey.allKeys] forModel:model];
  140. NSDictionary *dictionaryValue = [model.dictionaryValue dictionaryWithValuesForKeys:propertyKeysToSerialize.allObjects];
  141. NSMutableDictionary *JSONDictionary = [[NSMutableDictionary alloc] initWithCapacity:dictionaryValue.count];
  142. __block BOOL success = YES;
  143. __block NSError *tmpError = nil;
  144. [dictionaryValue enumerateKeysAndObjectsUsingBlock:^(NSString *propertyKey, id value, BOOL *stop) {
  145. id JSONKeyPaths = self.JSONKeyPathsByPropertyKey[propertyKey];
  146. if (JSONKeyPaths == nil) return;
  147. NSValueTransformer *transformer = self.valueTransformersByPropertyKey[propertyKey];
  148. if ([transformer.class allowsReverseTransformation]) {
  149. // Map NSNull -> nil for the transformer, and then back for the
  150. // dictionaryValue we're going to insert into.
  151. if ([value isEqual:NSNull.null]) value = nil;
  152. if ([transformer respondsToSelector:@selector(reverseTransformedValue:success:error:)]) {
  153. id<MTLTransformerErrorHandling> errorHandlingTransformer = (id)transformer;
  154. value = [errorHandlingTransformer reverseTransformedValue:value success:&success error:&tmpError];
  155. if (!success) {
  156. *stop = YES;
  157. return;
  158. }
  159. } else {
  160. value = [transformer reverseTransformedValue:value] ?: NSNull.null;
  161. }
  162. }
  163. void (^createComponents)(id, NSString *) = ^(id obj, NSString *keyPath) {
  164. NSArray *keyPathComponents = [keyPath componentsSeparatedByString:@"."];
  165. // Set up dictionaries at each step of the key path.
  166. for (NSString *component in keyPathComponents) {
  167. if ([obj valueForKey:component] == nil) {
  168. // Insert an empty mutable dictionary at this spot so that we
  169. // can set the whole key path afterward.
  170. [obj setValue:[NSMutableDictionary dictionary] forKey:component];
  171. }
  172. obj = [obj valueForKey:component];
  173. }
  174. };
  175. if ([JSONKeyPaths isKindOfClass:NSString.class]) {
  176. createComponents(JSONDictionary, JSONKeyPaths);
  177. [JSONDictionary setValue:value forKeyPath:JSONKeyPaths];
  178. }
  179. if ([JSONKeyPaths isKindOfClass:NSArray.class]) {
  180. for (NSString *JSONKeyPath in JSONKeyPaths) {
  181. createComponents(JSONDictionary, JSONKeyPath);
  182. [JSONDictionary setValue:value[JSONKeyPath] forKeyPath:JSONKeyPath];
  183. }
  184. }
  185. }];
  186. if (success) {
  187. return JSONDictionary;
  188. } else {
  189. if (error != NULL) *error = tmpError;
  190. return nil;
  191. }
  192. }
  193. - (id)modelFromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error {
  194. if ([self.modelClass respondsToSelector:@selector(classForParsingJSONDictionary:)]) {
  195. Class class = [self.modelClass classForParsingJSONDictionary:JSONDictionary];
  196. if (class == nil) {
  197. if (error != NULL) {
  198. NSDictionary *userInfo = @{
  199. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not parse JSON", @""),
  200. NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"No model class could be found to parse the JSON dictionary.", @"")
  201. };
  202. *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorNoClassFound userInfo:userInfo];
  203. }
  204. return nil;
  205. }
  206. if (class != self.modelClass) {
  207. NSAssert([class conformsToProtocol:@protocol(MTLJSONSerializing)], @"Class %@ returned from +classForParsingJSONDictionary: does not conform to <MTLJSONSerializing>", class);
  208. MTLJSONAdapter *otherAdapter = [self JSONAdapterForModelClass:class error:error];
  209. return [otherAdapter modelFromJSONDictionary:JSONDictionary error:error];
  210. }
  211. }
  212. NSMutableDictionary *dictionaryValue = [[NSMutableDictionary alloc] initWithCapacity:JSONDictionary.count];
  213. for (NSString *propertyKey in [self.modelClass propertyKeys]) {
  214. id JSONKeyPaths = self.JSONKeyPathsByPropertyKey[propertyKey];
  215. if (JSONKeyPaths == nil) continue;
  216. id value;
  217. if ([JSONKeyPaths isKindOfClass:NSArray.class]) {
  218. NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
  219. for (NSString *keyPath in JSONKeyPaths) {
  220. BOOL success = NO;
  221. id value = [JSONDictionary mtl_valueForJSONKeyPath:keyPath success:&success error:error];
  222. if (!success) return nil;
  223. if (value != nil) dictionary[keyPath] = value;
  224. }
  225. value = dictionary;
  226. } else {
  227. BOOL success = NO;
  228. value = [JSONDictionary mtl_valueForJSONKeyPath:JSONKeyPaths success:&success error:error];
  229. if (!success) return nil;
  230. }
  231. if (value == nil) continue;
  232. @try {
  233. NSValueTransformer *transformer = self.valueTransformersByPropertyKey[propertyKey];
  234. if (transformer != nil) {
  235. // Map NSNull -> nil for the transformer, and then back for the
  236. // dictionary we're going to insert into.
  237. if ([value isEqual:NSNull.null]) value = nil;
  238. if ([transformer respondsToSelector:@selector(transformedValue:success:error:)]) {
  239. id<MTLTransformerErrorHandling> errorHandlingTransformer = (id)transformer;
  240. BOOL success = YES;
  241. value = [errorHandlingTransformer transformedValue:value success:&success error:error];
  242. if (!success) return nil;
  243. } else {
  244. value = [transformer transformedValue:value];
  245. }
  246. if (value == nil) value = NSNull.null;
  247. }
  248. dictionaryValue[propertyKey] = value;
  249. } @catch (NSException *ex) {
  250. NSLog(@"*** Caught exception %@ parsing JSON key path \"%@\" from: %@", ex, JSONKeyPaths, JSONDictionary);
  251. // Fail fast in Debug builds.
  252. #if DEBUG
  253. @throw ex;
  254. #else
  255. if (error != NULL) {
  256. NSDictionary *userInfo = @{
  257. NSLocalizedDescriptionKey: ex.description,
  258. NSLocalizedFailureReasonErrorKey: ex.reason,
  259. MTLJSONAdapterThrownExceptionErrorKey: ex
  260. };
  261. *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorExceptionThrown userInfo:userInfo];
  262. }
  263. return nil;
  264. #endif
  265. }
  266. }
  267. id model = [self.modelClass modelWithDictionary:dictionaryValue error:error];
  268. return [model validate:error] ? model : nil;
  269. }
  270. + (NSDictionary *)valueTransformersForModelClass:(Class)modelClass {
  271. NSParameterAssert(modelClass != nil);
  272. NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);
  273. NSMutableDictionary *result = [NSMutableDictionary dictionary];
  274. for (NSString *key in [modelClass propertyKeys]) {
  275. SEL selector = MTLSelectorWithKeyPattern(key, "JSONTransformer");
  276. if ([modelClass respondsToSelector:selector]) {
  277. IMP imp = [modelClass methodForSelector:selector];
  278. NSValueTransformer * (*function)(id, SEL) = (__typeof__(function))imp;
  279. NSValueTransformer *transformer = function(modelClass, selector);
  280. if (transformer != nil) result[key] = transformer;
  281. continue;
  282. }
  283. if ([modelClass respondsToSelector:@selector(JSONTransformerForKey:)]) {
  284. NSValueTransformer *transformer = [modelClass JSONTransformerForKey:key];
  285. if (transformer != nil) result[key] = transformer;
  286. continue;
  287. }
  288. objc_property_t property = class_getProperty(modelClass, key.UTF8String);
  289. if (property == NULL) continue;
  290. mtl_propertyAttributes *attributes = mtl_copyPropertyAttributes(property);
  291. @onExit {
  292. free(attributes);
  293. };
  294. NSValueTransformer *transformer = nil;
  295. if (*(attributes->type) == *(@encode(id))) {
  296. Class propertyClass = attributes->objectClass;
  297. if (propertyClass != nil) {
  298. transformer = [self transformerForModelPropertiesOfClass:propertyClass];
  299. }
  300. // For user-defined MTLModel, try parse it with dictionaryTransformer.
  301. if (nil == transformer && [propertyClass conformsToProtocol:@protocol(MTLJSONSerializing)]) {
  302. transformer = [self dictionaryTransformerWithModelClass:propertyClass];
  303. }
  304. if (transformer == nil) transformer = [NSValueTransformer mtl_validatingTransformerForClass:propertyClass ?: NSObject.class];
  305. } else {
  306. transformer = [self transformerForModelPropertiesOfObjCType:attributes->type] ?: [NSValueTransformer mtl_validatingTransformerForClass:NSValue.class];
  307. }
  308. if (transformer != nil) result[key] = transformer;
  309. }
  310. return result;
  311. }
  312. - (MTLJSONAdapter *)JSONAdapterForModelClass:(Class)modelClass error:(NSError **)error {
  313. NSParameterAssert(modelClass != nil);
  314. NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);
  315. @synchronized(self) {
  316. MTLJSONAdapter *result = [self.JSONAdaptersByModelClass objectForKey:modelClass];
  317. if (result != nil) return result;
  318. result = [[self.class alloc] initWithModelClass:modelClass];
  319. if (result != nil) {
  320. [self.JSONAdaptersByModelClass setObject:result forKey:modelClass];
  321. }
  322. return result;
  323. }
  324. }
  325. - (NSSet *)serializablePropertyKeys:(NSSet *)propertyKeys forModel:(id<MTLJSONSerializing>)model {
  326. return propertyKeys;
  327. }
  328. + (NSValueTransformer *)transformerForModelPropertiesOfClass:(Class)modelClass {
  329. NSParameterAssert(modelClass != nil);
  330. SEL selector = MTLSelectorWithKeyPattern(NSStringFromClass(modelClass), "JSONTransformer");
  331. if (![self respondsToSelector:selector]) return nil;
  332. IMP imp = [self methodForSelector:selector];
  333. NSValueTransformer * (*function)(id, SEL) = (__typeof__(function))imp;
  334. NSValueTransformer *result = function(self, selector);
  335. return result;
  336. }
  337. + (NSValueTransformer *)transformerForModelPropertiesOfObjCType:(const char *)objCType {
  338. NSParameterAssert(objCType != NULL);
  339. if (strcmp(objCType, @encode(BOOL)) == 0) {
  340. return [NSValueTransformer valueTransformerForName:MTLBooleanValueTransformerName];
  341. }
  342. return nil;
  343. }
  344. @end
  345. @implementation MTLJSONAdapter (ValueTransformers)
  346. + (NSValueTransformer<MTLTransformerErrorHandling> *)dictionaryTransformerWithModelClass:(Class)modelClass {
  347. NSParameterAssert([modelClass isSubclassOfClass:MTLModel.class]);
  348. NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);
  349. __block MTLJSONAdapter *adapter;
  350. return [MTLValueTransformer
  351. transformerUsingForwardBlock:^ id (id JSONDictionary, BOOL *success, NSError **error) {
  352. if (JSONDictionary == nil) return nil;
  353. if (![JSONDictionary isKindOfClass:NSDictionary.class]) {
  354. if (error != NULL) {
  355. NSDictionary *userInfo = @{
  356. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert JSON dictionary to model object", @""),
  357. NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected an NSDictionary, got: %@", @""), JSONDictionary],
  358. MTLTransformerErrorHandlingInputValueErrorKey : JSONDictionary
  359. };
  360. *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
  361. }
  362. *success = NO;
  363. return nil;
  364. }
  365. if (!adapter) {
  366. adapter = [[self alloc] initWithModelClass:modelClass];
  367. }
  368. id model = [adapter modelFromJSONDictionary:JSONDictionary error:error];
  369. if (model == nil) {
  370. *success = NO;
  371. }
  372. return model;
  373. }
  374. reverseBlock:^ NSDictionary * (id model, BOOL *success, NSError **error) {
  375. if (model == nil) return nil;
  376. if (![model isKindOfClass:MTLModel.class] || ![model conformsToProtocol:@protocol(MTLJSONSerializing)]) {
  377. if (error != NULL) {
  378. NSDictionary *userInfo = @{
  379. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert model object to JSON dictionary", @""),
  380. NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected a MTLModel object conforming to <MTLJSONSerializing>, got: %@.", @""), model],
  381. MTLTransformerErrorHandlingInputValueErrorKey : model
  382. };
  383. *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
  384. }
  385. *success = NO;
  386. return nil;
  387. }
  388. if (!adapter) {
  389. adapter = [[self alloc] initWithModelClass:modelClass];
  390. }
  391. NSDictionary *result = [adapter JSONDictionaryFromModel:model error:error];
  392. if (result == nil) {
  393. *success = NO;
  394. }
  395. return result;
  396. }];
  397. }
  398. + (NSValueTransformer<MTLTransformerErrorHandling> *)arrayTransformerWithModelClass:(Class)modelClass {
  399. id<MTLTransformerErrorHandling> dictionaryTransformer = [self dictionaryTransformerWithModelClass:modelClass];
  400. return [MTLValueTransformer
  401. transformerUsingForwardBlock:^ id (NSArray *dictionaries, BOOL *success, NSError **error) {
  402. if (dictionaries == nil) return nil;
  403. if (![dictionaries isKindOfClass:NSArray.class]) {
  404. if (error != NULL) {
  405. NSDictionary *userInfo = @{
  406. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert JSON array to model array", @""),
  407. NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected an NSArray, got: %@.", @""), dictionaries],
  408. MTLTransformerErrorHandlingInputValueErrorKey : dictionaries
  409. };
  410. *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
  411. }
  412. *success = NO;
  413. return nil;
  414. }
  415. NSMutableArray *models = [NSMutableArray arrayWithCapacity:dictionaries.count];
  416. for (id JSONDictionary in dictionaries) {
  417. if (JSONDictionary == NSNull.null) {
  418. [models addObject:NSNull.null];
  419. continue;
  420. }
  421. if (![JSONDictionary isKindOfClass:NSDictionary.class]) {
  422. if (error != NULL) {
  423. NSDictionary *userInfo = @{
  424. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert JSON array to model array", @""),
  425. NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected an NSDictionary or an NSNull, got: %@.", @""), JSONDictionary],
  426. MTLTransformerErrorHandlingInputValueErrorKey : JSONDictionary
  427. };
  428. *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
  429. }
  430. *success = NO;
  431. return nil;
  432. }
  433. id model = [dictionaryTransformer transformedValue:JSONDictionary success:success error:error];
  434. if (*success == NO) return nil;
  435. if (model == nil) continue;
  436. [models addObject:model];
  437. }
  438. return models;
  439. }
  440. reverseBlock:^ id (NSArray *models, BOOL *success, NSError **error) {
  441. if (models == nil) return nil;
  442. if (![models isKindOfClass:NSArray.class]) {
  443. if (error != NULL) {
  444. NSDictionary *userInfo = @{
  445. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert model array to JSON array", @""),
  446. NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected an NSArray, got: %@.", @""), models],
  447. MTLTransformerErrorHandlingInputValueErrorKey : models
  448. };
  449. *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
  450. }
  451. *success = NO;
  452. return nil;
  453. }
  454. NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:models.count];
  455. for (id model in models) {
  456. if (model == NSNull.null) {
  457. [dictionaries addObject:NSNull.null];
  458. continue;
  459. }
  460. if (![model isKindOfClass:MTLModel.class]) {
  461. if (error != NULL) {
  462. NSDictionary *userInfo = @{
  463. NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert JSON array to model array", @""),
  464. NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected a MTLModel or an NSNull, got: %@.", @""), model],
  465. MTLTransformerErrorHandlingInputValueErrorKey : model
  466. };
  467. *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
  468. }
  469. *success = NO;
  470. return nil;
  471. }
  472. NSDictionary *dict = [dictionaryTransformer reverseTransformedValue:model success:success error:error];
  473. if (*success == NO) return nil;
  474. if (dict == nil) continue;
  475. [dictionaries addObject:dict];
  476. }
  477. return dictionaries;
  478. }];
  479. }
  480. + (NSValueTransformer *)NSURLJSONTransformer {
  481. return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
  482. }
  483. @end
  484. @implementation MTLJSONAdapter (Deprecated)
  485. #pragma clang diagnostic push
  486. #pragma clang diagnostic ignored "-Wdeprecated-implementations"
  487. + (NSArray *)JSONArrayFromModels:(NSArray *)models {
  488. return [self JSONArrayFromModels:models error:NULL];
  489. }
  490. + (NSDictionary *)JSONDictionaryFromModel:(MTLModel<MTLJSONSerializing> *)model {
  491. return [self JSONDictionaryFromModel:model error:NULL];
  492. }
  493. #pragma clang diagnostic pop
  494. @end