-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathCOItem+JSON.m
More file actions
310 lines (267 loc) · 8.47 KB
/
COItem+JSON.m
File metadata and controls
310 lines (267 loc) · 8.47 KB
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
/*
Copyright (C) 2013 Eric Wasylishen
Date: July 2013
License: MIT (see COPYING)
*/
#import "COItem+JSON.h"
#import <EtoileFoundation/EtoileFoundation.h>
#import "COPath.h"
#import "COAttachmentID.h"
#import "COJSONSerialization.h"
@implementation COItem (JSON)
// Semi-hack: we store the UUID alongside the real object properties.
// This property name is reserved for JSON serialization and cannot be used
// as an actual property name.
NSString *const kCOJSONObjectUUIDProperty = @"org.etoile-project.coreobject.uuid";
NSString *const kCOJSONFormatProperty = @"org.etoile-project.coreobject.json-format";
NSString *const kCOJSONFormat1_0 = @"1.0";
// COType -> string
static NSString *arraySuffix = @"-array";
static NSString *setSuffix = @"-set";
static NSString *intPrefix = @"int";
static NSString *floatPrefix = @"float";
static NSString *stringPrefix = @"string";
static NSString *blobPrefix = @"blob";
static NSString *referencePrefix = @"reference";
static NSString *compositePrefix = @"composite";
static NSString *attachmentPrefix = @"attachment";
static NSString *
COJSONMultivalueTypeToString(COType type)
{
if (COTypeIsMultivalued(type))
{
if (COTypeIsOrdered(type))
{
return arraySuffix;
}
else
{
return setSuffix;
}
}
return @"";
}
static NSString *
COJSONPrimitiveTypeToString(COType type)
{
switch (COTypePrimitivePart(type))
{
case kCOTypeInt64:
return intPrefix;
case kCOTypeDouble:
return floatPrefix;
case kCOTypeString:
return stringPrefix;
case kCOTypeBlob:
return blobPrefix;
case kCOTypeReference:
return referencePrefix;
case kCOTypeCompositeReference:
return compositePrefix;
case kCOTypeAttachment:
return attachmentPrefix;
default:
return @"";
}
}
NSString *
COJSONTypeToString(COType type)
{
NSCAssert(COTypeIsValid(type), @"type to serialize not valid");
return [COJSONPrimitiveTypeToString(type) stringByAppendingString: COJSONMultivalueTypeToString(type)];
}
// string -> COType
static COType
COJSONStringToType(NSString *type)
{
NSArray *components = [type componentsSeparatedByString: @"-"];
COType result = [@{intPrefix: @(kCOTypeInt64),
floatPrefix: @(kCOTypeDouble),
stringPrefix: @(kCOTypeString),
blobPrefix: @(kCOTypeBlob),
referencePrefix: @(kCOTypeReference),
compositePrefix: @(kCOTypeCompositeReference),
attachmentPrefix: @(kCOTypeAttachment)}[components[0]] intValue];
if ([type hasSuffix: setSuffix])
{
result |= kCOTypeSet;
}
else if ([type hasSuffix: arraySuffix])
{
result |= kCOTypeArray;
}
NSCAssert(COTypeIsValid(result), @"deserialized type not valid");
return result;
}
// COItem attribute value -> JSON-compatible plist
static id plistValueForPrimitiveValue(id aValue, COType aType)
{
if (aValue == [NSNull null])
{
return aValue;
}
switch (COTypePrimitivePart(aType))
{
case kCOTypeInt64:
return aValue;
case kCOTypeDouble:
return aValue;
case kCOTypeString:
return aValue;
case kCOTypeAttachment:
return [[aValue dataValue] base64String];
case kCOTypeBlob:
return [aValue base64String];
case kCOTypeCompositeReference:
return [aValue stringValue];
case kCOTypeReference:
if ([aValue isKindOfClass: [COPath class]])
{
return [@"path:" stringByAppendingString: [aValue stringValue]];
}
else
{
return [aValue stringValue];
}
default:
[NSException raise: NSInvalidArgumentException format: @"unknown type %d", aType];
return nil;
}
}
static id plistValueForValue(id aValue, COType aType)
{
NSString *typeString = COJSONTypeToString(aType);
if (COTypeIsUnivalued(aType))
{
return @{typeString: plistValueForPrimitiveValue(aValue, aType)};
}
else
{
NSMutableArray *collection = [NSMutableArray array];
for (id obj in aValue)
{
[collection addObject: plistValueForPrimitiveValue(obj, aType)];
}
return @{typeString: collection};
}
}
// JSON-compatible plist -> COItem attribute value
static id valueForPrimitivePlistValue(id aValue, COType aType)
{
if (aValue == [NSNull null])
{
return aValue;
}
switch (COTypePrimitivePart(aType))
{
case kCOTypeInt64:
return aValue;
case kCOTypeDouble:
return aValue;
case kCOTypeString:
return aValue;
case kCOTypeAttachment:
return [[COAttachmentID alloc] initWithData: [aValue base64DecodedData]];
case kCOTypeBlob:
return [aValue base64DecodedData];
case kCOTypeCompositeReference:
return [ETUUID UUIDWithString: aValue];
case kCOTypeReference:
if ([aValue hasPrefix: @"path:"])
{
return [COPath pathWithString: [aValue substringFromIndex: 5]];
}
else
{
return [ETUUID UUIDWithString: aValue];
}
default:
[NSException raise: NSInvalidArgumentException format: @"unknown type %d", aType];
return nil;
}
}
static id importValueFromPlist(id typeValuePair)
{
NSCAssert([typeValuePair count] == 1, @"JSON value dictionary should be one key : one value");
COType aType = COJSONStringToType([typeValuePair allKeys][0]);
id aValue = [typeValuePair allValues][0];
if (COTypeIsUnivalued(aType))
{
return valueForPrimitivePlistValue(aValue, aType);
}
else
{
id collection;
if (COTypeIsOrdered(aType))
{
collection = [NSMutableArray array];
}
else
{
collection = [NSMutableSet set];
}
for (id obj in aValue)
{
[collection addObject: valueForPrimitivePlistValue(obj, aType)];
}
return collection;
}
}
static COType importTypeFromPlist(id typeValuePair)
{
NSCAssert([typeValuePair count] == 1, @"JSON value dictionary should be one key : one value");
COType aType = COJSONStringToType([typeValuePair allKeys][0]);
return aType;
}
- (id)JSONPlist
{
NSMutableDictionary *plistValues = [NSMutableDictionary dictionaryWithCapacity: values.count];
for (NSString *key in values)
{
id plistValue = plistValueForValue(values[key], [types[key] intValue]);
plistValues[key] = plistValue;
}
ETAssert(plistValues[kCOJSONObjectUUIDProperty] == nil);
plistValues[kCOJSONObjectUUIDProperty] = [self.UUID stringValue];
ETAssert(plistValues[kCOJSONFormatProperty] == nil);
plistValues[kCOJSONFormatProperty] = kCOJSONFormat1_0;
return plistValues;
}
- (instancetype)initWithJSONPlist: (id)aPlist
{
ETUUID *aUUID = [ETUUID UUIDWithString: aPlist[kCOJSONObjectUUIDProperty]];
NSMutableDictionary *importedValues = [NSMutableDictionary dictionary];
NSMutableDictionary *importedTypes = [NSMutableDictionary dictionary];
// Check format
if (!(aPlist[kCOJSONFormatProperty] == nil // accept JSON written before format tag was added
|| [aPlist[kCOJSONFormatProperty] isEqual: kCOJSONFormat1_0]))
{
[NSException raise: NSInvalidArgumentException
format: @"Unknown COItem JSON format '%@'",
aPlist[kCOJSONFormatProperty]];
}
for (NSString *key in aPlist)
{
if ([key isEqualToString: kCOJSONObjectUUIDProperty]
|| [key isEqualToString: kCOJSONFormatProperty])
continue;
id typeValuePair = aPlist[key];
importedValues[key] = importValueFromPlist(typeValuePair);
importedTypes[key] = @(importTypeFromPlist(typeValuePair));
}
self = [self initWithUUID: aUUID
typesForAttributes: importedTypes
valuesForAttributes: importedValues];
return self;
}
- (NSData *)JSONData
{
id plist = self.JSONPlist;
return CODataWithJSONObject(plist, NULL);
}
- (instancetype)initWithJSONData: (NSData *)data
{
id plist = COJSONObjectWithData(data, NULL);
return [self initWithJSONPlist: plist];
}
@end