-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathJSONAPIResourceParser.m
389 lines (321 loc) · 14.3 KB
/
JSONAPIResourceParser.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
//
// JSONAPIResourceParser.m
// JSONAPI
//
// Created by Jonathan Karl Armstrong, 2015.
//
#import "JSONAPIResourceParser.h"
#import "JSONAPI.h"
#import "JSONAPIResourceDescriptor.h"
#import "JSONAPIPropertyDescriptor.h"
#pragma mark - JSONAPIResourceParser
@interface JSONAPIResourceParser()
/**
* Allocate a resource link instance from a deserialized JSON dictionary. The dictionary argument
* should describe one resource in JSON API format. This must be a JSON API "links" element.
* It is not a complete JSON API response block.
*
* This will return either a <JSONAPIResource> instance, or an NSArray of <JSONAPIResource> instances.
*
* @param dictionary deserialized JSON data object
*
* @return initialized resource instance.
*/
+ (id)jsonAPILink:(NSDictionary*)dictionary;
/**
* Generate a 'links' element for the data dictionary of the owner resource instance.
*
* A 'links' element contains the self link, a 'related' link that can be used to
* retrieve this instance given the container data, and a 'linkage' element that
* describes the minimum respresentation of this instance (type and ID).
*
* @param resource model object
* @param owner the resource instance that contains the model object
* @param key label used in JSON for **owner** property linked to the model object
*
* @return newly allocated JSON dictionary for linkage
*/
+ (NSDictionary*)link:(NSObject <JSONAPIResource>*)resource from:(NSObject <JSONAPIResource>*)owner withKey:(NSString*)key;
@end
@implementation JSONAPIResourceParser
#pragma mark -
#pragma mark - Class Methods
+ (id <JSONAPIResource>)parseResource:(NSDictionary*)dictionary {
NSString *type = dictionary[@"type"] ?: @"";
JSONAPIResourceDescriptor *descriptor = [JSONAPIResourceDescriptor forLinkedType:type];
NSObject <JSONAPIResource> *resource = [[[descriptor resourceClass] alloc] init];
[self set:resource withDictionary:dictionary];
return resource;
}
+ (NSArray*)parseResources:(NSArray*)array {
NSMutableArray *mutableArray = @[].mutableCopy;
for (NSDictionary *dictionary in array) {
NSObject <JSONAPIResource> *resource = [self parseResource:dictionary];
if(resource) {
[mutableArray addObject:resource];
}
}
return mutableArray;
}
+ (NSDictionary*)dictionaryFor:(NSObject <JSONAPIResource>*)resource {
NSFormatter *format;
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
// linkage is only allocated if there are 1 or more links
NSMutableDictionary *linkage = nil;
JSONAPIResourceDescriptor *descriptor = [[resource class] descriptor];
[dictionary setValue:[descriptor type] forKey: @"type"];
id ID = [resource valueForKey:[descriptor idProperty]];
if (ID) {
// ID optional only for create (POST request)
format = [descriptor idFormatter];
if (format) {
[dictionary setValue:[format stringForObjectValue:ID] forKey:@"id"];
} else {
[dictionary setValue:ID forKey:@"id"];
}
}
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
// Loops through all keys to map to properties
NSDictionary *properties = [descriptor properties];
for (NSString *key in properties) {
JSONAPIPropertyDescriptor *property = [properties objectForKey:key];
id value = [resource valueForKey:key];
if (value) {
if ([value isKindOfClass:[NSArray class]]) {
NSArray *valueArray = value;
if (valueArray.count > 0) {
NSMutableArray *dictionaryArray = [[NSMutableArray alloc] initWithCapacity:valueArray.count];
if ([property resourceType] || [((NSArray *)value).firstObject conformsToProtocol:@protocol(JSONAPIResource)]) {
if (linkage == nil) {
linkage = [[NSMutableDictionary alloc] init];
}
for (id valueElement in valueArray) {
[dictionaryArray addObject:[self link:valueElement from:resource withKey:[property jsonName]]];
}
NSDictionary *dataDictionary = @{@"data" : dictionaryArray};
[linkage setValue:dataDictionary forKey:[property jsonName]];
} else {
NSFormatter *format = [property formatter];
for (id valueElement in valueArray) {
if (format) {
[dictionaryArray addObject:[format stringForObjectValue:valueElement]];
} else {
[dictionaryArray addObject:valueElement];
}
}
[attributes setValue:dictionaryArray forKey:[property jsonName]];
}
}
} else {
if ([property resourceType] || [value conformsToProtocol:@protocol(JSONAPIResource)]) {
if (linkage == nil) {
linkage = [[NSMutableDictionary alloc] init];
}
NSObject <JSONAPIResource> *attribute = value;
[linkage setValue:[self link:attribute from:resource withKey:[property jsonName]] forKey:[property jsonName]];
} else {
format = [property formatter];
if (format) {
[attributes setValue:[format stringForObjectValue:value] forKey:[property jsonName]];
} else {
[attributes setValue:value forKey:[property jsonName]];
}
}
}
}
}
if (attributes.count > 0) {
[dictionary setValue:attributes forKey:@"attributes"];
}
if (linkage) {
[dictionary setValue:linkage forKey:@"relationships"];
}
// TODO: Need to also add in all other links
if (resource.selfLink) {
dictionary[@"links"] = @{ @"self": resource.selfLink };
}
return dictionary;
}
+ (void)set:(NSObject <JSONAPIResource> *)resource withDictionary:dictionary {
NSString *error;
JSONAPIResourceDescriptor *descriptor = [[resource class] descriptor];
NSDictionary *relationships = [dictionary objectForKey:@"relationships"];
NSDictionary *attributes = [dictionary objectForKey:@"attributes"];
NSDictionary *links = [dictionary objectForKey:@"links"];
NSDictionary *meta = [dictionary objectForKey:@"meta"];
id ID = [dictionary objectForKey:@"id"];
NSFormatter *format = [descriptor idFormatter];
if (format) {
id xformed;
if ([format getObjectValue:&xformed forString:ID errorDescription:&error]) {
[resource setValue:xformed forKey:[descriptor idProperty]];
}
} else {
[resource setValue:ID forKey:[descriptor idProperty]];
}
if (descriptor.selfLinkProperty) {
NSString *selfLink = links[@"self"];
[resource setValue:selfLink forKey:descriptor.selfLinkProperty];
}
if ([resource respondsToSelector:@selector(setMeta:)]) {
[resource setMeta:meta];
}
// Loops through all keys to map to properties
NSDictionary *properties = [descriptor properties];
for (NSString *key in properties) {
JSONAPIPropertyDescriptor *property = [properties objectForKey:key];
if (property.resourceType) {
if (relationships) {
id value = [relationships objectForKey:[property jsonName]];
if (value[@"data"] != [NSNull null]) {
[resource setValue:[JSONAPIResourceParser jsonAPILink:value] forKey:key];
}
}
} else if (relationships[key]) {
if (relationships) {
id value = relationships[key];
if (value[@"data"] != [NSNull null]) {
[resource setValue:[JSONAPIResourceParser jsonAPILink:value] forKey:key];
}
}
} else {
id value = [attributes objectForKey:[property jsonName]];
if ((id)[NSNull null] == value) {
value = [dictionary objectForKey:[property jsonName]];
}
if (value) {
// anything else should be a value property
format = [property formatter];
if ([value isKindOfClass:[NSArray class]]) {
if (format) {
NSMutableArray *temp = [value mutableCopy];
for (int i = 0; i < [value count]; ++i) {
id xformed;
if ([format getObjectValue:&xformed forString:temp[i] errorDescription:&error]) {
temp[i] = xformed;
}
}
[resource setValue:temp forKey:key];
} else {
[resource setValue:[[NSArray alloc] initWithArray:value] forKey:key];
}
} else {
if (format) {
id xformed;
if ([format getObjectValue:&xformed forString:value errorDescription:&error]) {
[resource setValue:xformed forKey:key];
}
} else {
[resource setValue:value forKey:key];
}
}
}
}
}
}
+ (id)jsonAPILink:(NSDictionary*)dictionary {
id linkage = dictionary[@"data"];
if ([linkage isKindOfClass:[NSArray class]]) {
NSMutableArray *linkArray = [[NSMutableArray alloc] initWithCapacity:[linkage count]];
for (NSDictionary *linkElement in linkage) {
[linkArray addObject:[JSONAPIResourceParser parseResource:linkElement]];
}
return linkArray;
} else {
return [JSONAPIResourceParser parseResource:linkage];
}
}
+ (void)link:(NSObject <JSONAPIResource>*)resource withIncluded:(JSONAPI*)jsonAPI {
NSDictionary *included = jsonAPI.includedResources;
if (nil == included) return;
JSONAPIResourceDescriptor *descriptor = [[resource class] descriptor];
// Loops through all keys to map to properties
NSDictionary *properties = [descriptor properties];
for (NSString *key in properties) {
JSONAPIPropertyDescriptor *propertyDescriptor = [properties objectForKey:key];
id value = [resource valueForKey:key];
Class valueClass = nil;
if (propertyDescriptor.resourceType) {
valueClass = propertyDescriptor.resourceType;
} else if ([value conformsToProtocol:@protocol(JSONAPIResource)] || [value isKindOfClass:[NSArray class]]) {
valueClass = [value class];
}
// ordinary attribute
if (valueClass == nil) {
continue;
// has many
} else if ([value isKindOfClass:[NSArray class]]) {
NSMutableArray *matched = [value mutableCopy];
[value enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj conformsToProtocol:@protocol(JSONAPIResource)]) {
NSObject <JSONAPIResource> *res = obj;
id includedValue = included[[[res.class descriptor] type]];
if (includedValue) {
id v = includedValue[res.ID];
if (v != nil) {
matched[idx] = v;
}
}
}
}];
[resource setValue:matched forKey:key];
// has one
} else if (value != nil) {
if ([value conformsToProtocol:@protocol(JSONAPIResource)]) {
id <JSONAPIResource> res = value;
id includedValue = included[[[res.class descriptor] type]];
if (includedValue) {
id v = included[[[res.class descriptor] type]][res.ID];
if (v != nil) {
[resource setValue:v forKey:key];
}
}
}
}
}
}
+ (NSArray*)relatedResourcesFor:(NSObject <JSONAPIResource>*)resource {
NSMutableArray *related = [[NSMutableArray alloc] init];
JSONAPIResourceDescriptor *descriptor = [[resource class] descriptor];
// Loops through all keys to map to properties
NSDictionary *properties = [descriptor properties];
for (NSString *key in properties) {
JSONAPIPropertyDescriptor *property = [properties objectForKey:key];
if (property.resourceType) {
id value = [resource valueForKey:key];
if ([value isKindOfClass:[NSArray class]]) {
[related addObjectsFromArray:value];
} else {
[related addObject:value];
}
}
}
return related;
}
+ (NSDictionary*)link:(NSObject <JSONAPIResource>*)resource from:(NSObject <JSONAPIResource>*)owner withKey:(NSString*)key {
JSONAPIResourceDescriptor *descriptor = [[resource class] descriptor];
NSMutableDictionary *reference = [[NSMutableDictionary alloc] init];
if (owner.selfLink) {
NSMutableString *link_to_self = [owner.selfLink mutableCopy];
[link_to_self appendString:@"/links/"];
[link_to_self appendString:key];
[reference setValue:link_to_self forKey:@"self"];
NSMutableString *related = [owner.selfLink mutableCopy];
[related appendString:@"/"];
[related appendString:key];
[reference setValue:related forKey:@"related"];
}
if (resource.ID) {
NSDictionary *referenceObject = @{
@"type" : descriptor.type,
@"id" : resource.ID
};
if ([[owner valueForKey:key] isKindOfClass:[NSArray class]]) {
reference = referenceObject.mutableCopy;
} else {
[reference setValue:referenceObject forKey:@"data"];
}
}
return reference;
}
@end