123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770 |
- /* Copyright (c) 2011 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #include <objc/runtime.h>
- //#import <GoogleAPIClientForREST/GTLRObject.h>
- //#import <GoogleAPIClientForREST/GTLRRuntimeCommon.h>
- //#import <GoogleAPIClientForREST/GTLRUtilities.h>
- #import "GTLRObject.h"
- #import "GTLRRuntimeCommon.h"
- #import "GTLRUtilities.h"
- #import "GTLRDefines.h"
- static NSString *const kUserDataPropertyKey = @"_userData";
- static NSString *const kGTLRObjectJSONCoderKey = @"json";
- static NSMutableDictionary *DeepMutableCopyOfJSONDictionary(NSDictionary *initialJSON);
- @interface GTLRObject () <GTLRRuntimeCommon>
- @property(nonatomic, strong) id<GTLRObjectClassResolver>objectClassResolver;
- @end
- @implementation GTLRObject {
- // Any complex object hung off this object goes into the cache so the
- // next fetch will get the same object back instead of having to recreate
- // it.
- NSMutableDictionary *_childCache;
- }
- @synthesize JSON = _json,
- objectClassResolver = _objectClassResolver,
- userProperties = _userProperties;
- + (instancetype)object {
- return [[self alloc] init];
- }
- + (instancetype)objectWithJSON:(NSDictionary *)dict {
- GTLRObject *obj = [self object];
- obj->_json = DeepMutableCopyOfJSONDictionary(dict);
- return obj;
- }
- + (instancetype)objectWithJSON:(nullable NSDictionary *)dict
- objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver {
- GTLRObject *obj = [self objectWithJSON:dict];
- obj->_objectClassResolver = objectClassResolver;
- return obj;
- }
- + (NSDictionary<NSString *, NSString *> *)propertyToJSONKeyMap {
- return nil;
- }
- + (NSDictionary<NSString *, Class> *)arrayPropertyToClassMap {
- return nil;
- }
- + (Class)classForAdditionalProperties {
- return Nil;
- }
- + (BOOL)isKindValidForClassRegistry {
- return YES;
- }
- - (BOOL)isEqual:(GTLRObject *)other {
- if (self == other) return YES;
- if (other == nil) return NO;
- // The objects should be the same class, or one should be a subclass of the
- // other's class
- if (![other isKindOfClass:[self class]]
- && ![self isKindOfClass:[other class]]) return NO;
- // What we're not comparing here:
- // properties
- return GTLR_AreEqualOrBothNil(_json, [other JSON]);
- }
- // By definition, for two objects to potentially be considered equal,
- // they must have the same hash value. The hash is mostly ignored,
- // but removeObjectsInArray: in Leopard does seem to check the hash,
- // and NSObject's default hash method just returns the instance pointer.
- // We'll define hash here for all of our GTLRObjects.
- - (NSUInteger)hash {
- return (NSUInteger) (__bridge void *) [GTLRObject class];
- }
- - (id)copyWithZone:(NSZone *)zone {
- GTLRObject *newObject = [[[self class] allocWithZone:zone] init];
- newObject.JSON = DeepMutableCopyOfJSONDictionary(self.JSON);
- newObject.objectClassResolver = self.objectClassResolver;
- // What we're not copying:
- // userProperties
- return newObject;
- }
- - (NSString *)descriptionWithLocale:(id)locale {
- return self.description;
- }
- + (BOOL)supportsSecureCoding {
- return YES;
- }
- - (instancetype)initWithCoder:(NSCoder *)decoder {
- self = [super init];
- if (self) {
- // NSDictionary/NSArray seem to allow strings and numbers with secure coding
- // just fine, but to allow sub arrays or dictionaries (or an null) the
- // classes have to be explicitly listed to decode correctly.
- NSSet *expectedClasses =
- [NSSet setWithObjects:
- [NSMutableDictionary class], [NSMutableArray class], [NSNull class], nil];
- _json = [decoder decodeObjectOfClasses:expectedClasses
- forKey:kGTLRObjectJSONCoderKey];
- }
- return self;
- }
- - (void)encodeWithCoder:(NSCoder *)encoder {
- [encoder encodeObject:_json forKey:kGTLRObjectJSONCoderKey];
- }
- #pragma mark JSON values
- - (void)setJSONValue:(id)obj forKey:(NSString *)key {
- NSMutableDictionary *dict = self.JSON;
- if (dict == nil && obj != nil) {
- dict = [NSMutableDictionary dictionaryWithCapacity:1];
- self.JSON = dict;
- }
- [dict setValue:obj forKey:key];
- }
- - (id)JSONValueForKey:(NSString *)key {
- id obj = [self.JSON objectForKey:key];
- return obj;
- }
- - (NSString *)JSONString {
- NSError *error;
- NSDictionary *json = self.JSON;
- if (json) {
- NSData *data = [NSJSONSerialization dataWithJSONObject:json
- options:NSJSONWritingPrettyPrinted
- error:&error];
- GTLR_DEBUG_ASSERT(data != nil, @"JSONString generate failed: %@\n JSON: %@", error, json);
- if (data) {
- NSString *jsonStr = [[NSString alloc] initWithData:data
- encoding:NSUTF8StringEncoding];
- if (jsonStr) return jsonStr;
- }
- }
- return @"";
- }
- - (NSArray<NSString *> *)additionalJSONKeys {
- NSArray *knownKeys = [[self class] allKnownKeys];
- NSMutableArray *result;
- NSArray *allKeys = _json.allKeys;
- if (allKeys) {
- result = [NSMutableArray arrayWithArray:allKeys];
- [result removeObjectsInArray:knownKeys];
- // Return nil instead of an empty array.
- if (result.count == 0) {
- result = nil;
- }
- }
- return result;
- }
- #pragma mark Partial - Fields
- - (NSString *)fieldsDescription {
- NSString *str = [GTLRObject fieldsDescriptionForJSON:self.JSON];
- return str;
- }
- + (NSString *)fieldsDescriptionForJSON:(NSDictionary *)targetJSON {
- // Internal routine: recursively generate a string field description
- // by joining elements
- NSArray *array = [self fieldsElementsForJSON:targetJSON];
- NSString *str = [array componentsJoinedByString:@","];
- return str;
- }
- + (NSArray *)fieldsElementsForJSON:(NSDictionary *)targetJSON {
- // Internal routine: recursively generate an array of field description
- // element strings
- NSMutableArray *resultFields = [NSMutableArray array];
- // Sorting the dictionary keys gives us deterministic results when iterating
- NSArray *sortedKeys = [targetJSON.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
- for (NSString *key in sortedKeys) {
- // We'll build a comma-separated list of fields
- id value = [targetJSON objectForKey:key];
- if ([value isKindOfClass:[NSString class]]
- || [value isKindOfClass:[NSNumber class]]) {
- // Basic type (string, number), so the key is what we want
- [resultFields addObject:key];
- } else if ([value isKindOfClass:[NSDictionary class]]) {
- // Object (dictionary): "parent/child1,parent/child2,parent/child3"
- NSArray *subElements = [self fieldsElementsForJSON:value];
- for (NSString *subElem in subElements) {
- NSString *prepended = [NSString stringWithFormat:@"%@/%@",
- key, subElem];
- [resultFields addObject:prepended];
- }
- } else if ([value isKindOfClass:[NSArray class]]) {
- // Array; we'll generate from the first array entry:
- // "parent(child1,child2,child3)"
- //
- // Open question: should this instead create the union of elements for
- // all items in the array, rather than just get fields from the first
- // array object?
- if (((NSArray *)value).count > 0) {
- id firstObj = [value objectAtIndex:0];
- if ([firstObj isKindOfClass:[NSDictionary class]]) {
- // An array of objects
- NSString *contentsStr = [self fieldsDescriptionForJSON:firstObj];
- NSString *encapsulated = [NSString stringWithFormat:@"%@(%@)",
- key, contentsStr];
- [resultFields addObject:encapsulated];
- } else {
- // An array of some basic type, or of arrays
- [resultFields addObject:key];
- }
- }
- } else {
- NSAssert(0, @"GTLRObject unknown field element for %@ (%@)",
- key, NSStringFromClass([value class]));
- }
- }
- return resultFields;
- }
- #pragma mark Partial - Patch
- - (id)patchObjectFromOriginal:(GTLRObject *)original {
- GTLRObject *resultObj;
- NSMutableDictionary *resultJSON = [GTLRObject patchDictionaryForJSON:self.JSON
- fromOriginalJSON:original.JSON];
- if (resultJSON.count > 0) {
- // Avoid an extra copy by assigning the JSON directly rather than using +objectWithJSON:
- resultObj = [[self class] object];
- resultObj.JSON = resultJSON;
- } else {
- // Client apps should not attempt to patch with an object containing
- // empty JSON
- resultObj = nil;
- }
- return resultObj;
- }
- + (NSMutableDictionary *)patchDictionaryForJSON:(NSDictionary *)newJSON
- fromOriginalJSON:(NSDictionary *)originalJSON {
- // Internal recursive routine to create an object suitable for
- // our patch semantics
- NSMutableDictionary *resultJSON = [NSMutableDictionary dictionary];
- // Iterate through keys present in the old object
- NSArray *originalKeys = originalJSON.allKeys;
- for (NSString *key in originalKeys) {
- id originalValue = [originalJSON objectForKey:key];
- id newValue = [newJSON valueForKey:key];
- if (newValue == nil) {
- // There is no new value for this key, so set the value to NSNull
- [resultJSON setValue:[NSNull null] forKey:key];
- } else if (!GTLR_AreEqualOrBothNil(originalValue, newValue)) {
- // The values for this key differ
- if ([originalValue isKindOfClass:[NSDictionary class]]
- && [newValue isKindOfClass:[NSDictionary class]]) {
- // Both are objects; recurse
- NSMutableDictionary *subDict = [self patchDictionaryForJSON:newValue
- fromOriginalJSON:originalValue];
- [resultJSON setValue:subDict forKey:key];
- } else {
- // They are non-object values; the new replaces the old. Per the
- // documentation for patch, this replaces entire arrays.
- [resultJSON setValue:newValue forKey:key];
- }
- } else {
- // The values are the same; omit this key-value pair
- }
- }
- // Iterate through keys present only in the new object, and add them to the
- // result
- NSMutableArray *newKeys = [NSMutableArray arrayWithArray:newJSON.allKeys];
- [newKeys removeObjectsInArray:originalKeys];
- for (NSString *key in newKeys) {
- id value = [newJSON objectForKey:key];
- [resultJSON setValue:value forKey:key];
- }
- return resultJSON;
- }
- + (id)nullValue {
- return [NSNull null];
- }
- #pragma mark Additional Properties
- - (id)additionalPropertyForName:(NSString *)name {
- // Return the cached object, if any, before creating one.
- id result = [self cacheChildForKey:name];
- if (result != nil) {
- return result;
- }
- Class defaultClass = [[self class] classForAdditionalProperties];
- id jsonObj = [self JSONValueForKey:name];
- BOOL shouldCache = NO;
- if (jsonObj != nil) {
- id<GTLRObjectClassResolver>objectClassResolver = self.objectClassResolver;
- result = [GTLRRuntimeCommon objectFromJSON:jsonObj
- defaultClass:defaultClass
- objectClassResolver:objectClassResolver
- isCacheable:&shouldCache];
- }
- [self setCacheChild:(shouldCache ? result : nil)
- forKey:name];
- return result;
- }
- - (void)setAdditionalProperty:(id)obj forName:(NSString *)name {
- BOOL shouldCache = NO;
- Class defaultClass = [[self class] classForAdditionalProperties];
- id json = [GTLRRuntimeCommon jsonFromAPIObject:obj
- expectedClass:defaultClass
- isCacheable:&shouldCache];
- [self setJSONValue:json forKey:name];
- [self setCacheChild:(shouldCache ? obj : nil)
- forKey:name];
- }
- - (NSDictionary<NSString *, id> *)additionalProperties {
- NSMutableDictionary *result = [NSMutableDictionary dictionary];
- NSArray *propertyNames = [self additionalJSONKeys];
- for (NSString *name in propertyNames) {
- id obj = [self additionalPropertyForName:name];
- [result setObject:obj forKey:name];
- }
- return result;
- }
- #pragma mark Child Cache methods
- // There is no property for _childCache as there shouldn't be KVC/KVO
- // support for it, it's an implementation detail.
- - (void)setCacheChild:(id)obj forKey:(NSString *)key {
- if (_childCache == nil && obj != nil) {
- _childCache = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
- obj, key, nil];
- } else {
- [_childCache setValue:obj forKey:key];
- }
- }
- - (id)cacheChildForKey:(NSString *)key {
- id obj = [_childCache objectForKey:key];
- return obj;
- }
- #pragma mark Support methods
- + (NSMutableArray *)allDeclaredProperties {
- NSMutableArray *array = [NSMutableArray array];
- // walk from this class up the hierarchy to GTLRObject
- Class topClass = class_getSuperclass([GTLRObject class]);
- for (Class currClass = self;
- currClass != topClass;
- currClass = class_getSuperclass(currClass)) {
- // step through this class's properties, and add the property names to the
- // array
- objc_property_t *properties = class_copyPropertyList(currClass, NULL);
- if (properties) {
- for (objc_property_t *prop = properties;
- *prop != NULL;
- ++prop) {
- const char *propName = property_getName(*prop);
- // We only want dynamic properties; their attributes contain ",D".
- const char *attr = property_getAttributes(*prop);
- const char *dynamicMarker = strstr(attr, ",D");
- if (dynamicMarker &&
- (dynamicMarker[2] == 0 || dynamicMarker[2] == ',' )) {
- [array addObject:(id _Nonnull)@(propName)];
- }
- }
- free(properties);
- }
- }
- return array;
- }
- + (NSArray *)allKnownKeys {
- NSArray *allProps = [self allDeclaredProperties];
- NSMutableArray *knownKeys = [NSMutableArray arrayWithArray:allProps];
- NSDictionary *propMap = [GTLRObject propertyToJSONKeyMapForClass:[self class]];
- NSUInteger idx = 0;
- for (NSString *propName in allProps) {
- NSString *jsonKey = [propMap objectForKey:propName];
- if (jsonKey) {
- [knownKeys replaceObjectAtIndex:idx
- withObject:jsonKey];
- }
- ++idx;
- }
- return knownKeys;
- }
- - (NSString *)description {
- NSString *jsonDesc = [self JSONDescription];
- NSString *str = [NSString stringWithFormat:@"%@ %p: %@",
- [self class], self, jsonDesc];
- return str;
- }
- // Internal utility for creating an appropriate description summary for the object's JSON.
- - (NSString *)JSONDescription {
- // Find the list of declared and otherwise known JSON keys for this class.
- NSArray *knownKeys = [[self class] allKnownKeys];
- NSMutableString *descStr = [NSMutableString stringWithString:@"{"];
- NSString *spacer = @"";
- for (NSString *key in [[_json allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]) {
- NSString *value = nil;
- // show question mark for JSON keys not supported by a declared property:
- // foo?:"Hi mom."
- NSString *qmark = [knownKeys containsObject:key] ? @"" : @"?";
- // determine property value to dislay
- id rawValue = [_json valueForKey:key];
- if ([rawValue isKindOfClass:[NSDictionary class]]) {
- // for dictionaries, show the list of keys:
- // {key1,key2,key3}
- NSArray *subKeys = [((NSDictionary *)rawValue).allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
- NSString *subkeyList = [subKeys componentsJoinedByString:@","];
- value = [NSString stringWithFormat:@"{%@}", subkeyList];
- } else if ([rawValue isKindOfClass:[NSArray class]]) {
- // for arrays, show the number of items in the array:
- // [3]
- value = [NSString stringWithFormat:@"[%lu]",
- (unsigned long)((NSArray *)rawValue).count];
- } else if ([rawValue isKindOfClass:[NSString class]]) {
- // for strings, show the string in quotes:
- // "Hi mom."
- value = [NSString stringWithFormat:@"\"%@\"", rawValue];
- } else {
- // for numbers, show just the number
- value = [rawValue description];
- }
- [descStr appendFormat:@"%@%@%@:%@", spacer, key, qmark, value];
- spacer = @" ";
- }
- [descStr appendString:@"}"];
- return descStr;
- }
- #pragma mark Object Instantiation
- + (GTLRObject *)objectForJSON:(NSMutableDictionary *)json
- defaultClass:(Class)defaultClass
- objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver {
- if (((id)json == [NSNull null]) || json.count == 0) {
- if (json != nil && defaultClass != Nil) {
- // The JSON included an empty dictionary, just create the object.
- Class classToCreate =
- GTLRObjectResolveClass(objectClassResolver,
- [NSDictionary dictionary],
- defaultClass);
- return [classToCreate object];
- }
- // No actual result, such as the response from a delete.
- return nil;
- }
- if (defaultClass == Nil) {
- defaultClass = self;
- }
- Class classToCreate =
- GTLRObjectResolveClass(objectClassResolver, json, defaultClass);
- // now instantiate the GTLRObject
- GTLRObject *parsedObject = [classToCreate object];
- parsedObject.objectClassResolver = objectClassResolver;
- parsedObject.JSON = json;
- return parsedObject;
- }
- #pragma mark Runtime Utilities
- static NSMutableDictionary *gJSONKeyMapCache = nil;
- static NSMutableDictionary *gArrayPropertyToClassMapCache = nil;
- + (void)initialize {
- // Note that initialize is guaranteed by the runtime to be called in a
- // thread-safe manner
- if (gJSONKeyMapCache == nil) {
- gJSONKeyMapCache = [[NSMutableDictionary alloc] init];
- }
- if (gArrayPropertyToClassMapCache == nil) {
- gArrayPropertyToClassMapCache = [[NSMutableDictionary alloc] init];
- }
- }
- + (NSDictionary *)propertyToJSONKeyMapForClass:(Class<GTLRRuntimeCommon>)aClass {
- NSDictionary *resultMap =
- [GTLRRuntimeCommon mergedClassDictionaryForSelector:@selector(propertyToJSONKeyMap)
- startClass:aClass
- ancestorClass:[GTLRObject class]
- cache:gJSONKeyMapCache];
- return resultMap;
- }
- + (NSDictionary *)arrayPropertyToClassMapForClass:(Class<GTLRRuntimeCommon>)aClass {
- NSDictionary *resultMap =
- [GTLRRuntimeCommon mergedClassDictionaryForSelector:@selector(arrayPropertyToClassMap)
- startClass:aClass
- ancestorClass:[GTLRObject class]
- cache:gArrayPropertyToClassMapCache];
- return resultMap;
- }
- #pragma mark Runtime Support
- + (Class<GTLRRuntimeCommon>)ancestorClass {
- return [GTLRObject class];
- }
- + (BOOL)resolveInstanceMethod:(SEL)sel {
- BOOL resolved = [GTLRRuntimeCommon resolveInstanceMethod:sel onClass:self];
- if (resolved)
- return YES;
- return [super resolveInstanceMethod:sel];
- }
- @end
- @implementation GTLRCollectionObject
- + (NSString *)collectionItemsKey {
- // GTLRCollectionObject fast enumeration, indexed access, and automatic pagination
- // (when shouldFetchNextPages is enabled) applies to the object array property "items".
- // The array property's key may be different if subclasses override this method.
- return @"items";
- }
- - (id)objectAtIndexedSubscript:(NSUInteger)idx {
- NSString *key = [[self class] collectionItemsKey];
- NSArray *items = [self valueForKey:key];
- if (items == nil) {
- [NSException raise:NSRangeException
- format:@"index %lu beyond bounds (%@ property \"%@\" is nil)",
- (unsigned long)idx, [self class], key];
- }
- id result = [items objectAtIndexedSubscript:idx];
- return result;
- }
- // NSFastEnumeration protocol
- - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
- objects:(__unsafe_unretained id _Nonnull *)stackbuf
- count:(NSUInteger)len {
- NSString *key = [[self class] collectionItemsKey];
- NSArray *items = [self valueForKey:key];
- NSUInteger result = [items countByEnumeratingWithState:state
- objects:stackbuf
- count:len];
- return result;
- }
- @end
- @implementation GTLRDataObject
- @synthesize data = _data,
- contentType = _contentType;
- - (NSString *)description {
- NSString *jsonDesc = @"";
- if (self.JSON.count > 0) {
- jsonDesc = [self JSONDescription];
- }
- return [NSString stringWithFormat:@"%@ %p: %lu bytes, contentType:%@ %@",
- [self class], self, (unsigned long)self.data.length, self.contentType,
- jsonDesc];
- }
- - (id)copyWithZone:(NSZone *)zone {
- GTLRDataObject *newObj = [super copyWithZone:zone];
- newObj.data = [self.data copy];
- newObj.contentType = self.contentType;
- return newObj;
- }
- @end
- @implementation GTLRResultArray
- - (NSArray *)itemsWithItemClass:(Class)itemClass {
- // Return the cached array before creating on demand.
- NSString *cacheKey = @"result_array_items";
- NSMutableArray *cachedArray = [self cacheChildForKey:cacheKey];
- if (cachedArray != nil) {
- return cachedArray;
- }
- NSArray *result = nil;
- NSArray *array = (NSArray *)self.JSON;
- if (array != nil) {
- if ([array isKindOfClass:[NSArray class]]) {
- id<GTLRObjectClassResolver>objectClassResolver = self.objectClassResolver;
- result = [GTLRRuntimeCommon objectFromJSON:array
- defaultClass:itemClass
- objectClassResolver:objectClassResolver
- isCacheable:NULL];
- } else {
- #if DEBUG
- if (![array isKindOfClass:[NSNull class]]) {
- GTLR_DEBUG_LOG(@"GTLRObject: unexpected JSON: %@ should be an array, actually is a %@:\n%@",
- NSStringFromClass([self class]),
- NSStringFromClass([array class]),
- array);
- }
- #endif
- result = array;
- }
- }
- [self setCacheChild:result forKey:cacheKey];
- return result;
- }
- - (NSString *)JSONDescription {
- // Just like GTLRObject's handing of arrays, just return the count.
- return [NSString stringWithFormat:@"[%lu]", (unsigned long)self.JSON.count];
- }
- @end
- Class GTLRObjectResolveClass(
- id<GTLRObjectClassResolver>objectClassResolver,
- NSDictionary *json,
- Class defaultClass) {
- Class result = [objectClassResolver classForJSON:json
- defaultClass:defaultClass];
- if (result == Nil) {
- result = defaultClass;
- }
- return result;
- }
- @implementation GTLRObjectClassResolver {
- NSDictionary<NSString *, Class> *_kindToClassMap;
- NSDictionary<Class, Class> *_surrogates;
- }
- + (instancetype)resolverWithKindMap:(NSDictionary<NSString *, Class> *)kindStringToClassMap {
- GTLRObjectClassResolver *result = [[self alloc] initWithKindMap:kindStringToClassMap
- surrogates:nil];
- return result;
- }
- + (instancetype)resolverWithKindMap:(NSDictionary<NSString *, Class> *)kindStringToClassMap
- surrogates:(NSDictionary<Class, Class> *)surrogates {
- GTLRObjectClassResolver *result = [[self alloc] initWithKindMap:kindStringToClassMap
- surrogates:surrogates];
- return result;
- }
- - (instancetype)initWithKindMap:(NSDictionary<NSString *, Class> *)kindStringToClassMap
- surrogates:(NSDictionary<Class, Class> *)surrogates {
- self = [super init];
- if (self) {
- _kindToClassMap = [kindStringToClassMap copy];
- _surrogates = [surrogates copy];
- }
- return self;
- }
- - (Class)classForJSON:(NSDictionary *)json
- defaultClass:(Class)defaultClass {
- Class result = defaultClass;
- // Apply kind map.
- BOOL shouldUseKind = (result == Nil) || [result isKindValidForClassRegistry];
- if (shouldUseKind && [json isKindOfClass:[NSDictionary class]]) {
- NSString *kind = [json valueForKey:@"kind"];
- if ([kind isKindOfClass:[NSString class]] && kind.length > 0) {
- Class dynamicClass = [_kindToClassMap objectForKey:kind];
- if (dynamicClass) {
- result = dynamicClass;
- }
- }
- }
- // Apply surrogate map.
- Class surrogate = [_surrogates objectForKey:result];
- if (surrogate) {
- result = surrogate;
- }
- return result;
- }
- @end
- static NSMutableDictionary *DeepMutableCopyOfJSONDictionary(NSDictionary *initialJSON) {
- if (!initialJSON) return nil;
- NSMutableDictionary *result;
- CFPropertyListRef ref = CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
- (__bridge CFPropertyListRef)(initialJSON),
- kCFPropertyListMutableContainers);
- if (ref) {
- result = CFBridgingRelease(ref);
- } else {
- // Failed to copy, probably due to a non-plist type such as NSNull.
- //
- // As a fallback, round-trip through NSJSONSerialization.
- NSError *serializationError;
- NSData *data = [NSJSONSerialization dataWithJSONObject:initialJSON
- options:0
- error:&serializationError];
- if (!data) {
- GTLR_DEBUG_ASSERT(0, @"Copy failed due to serialization: %@\nJSON: %@",
- serializationError, initialJSON);
- } else {
- result = [NSJSONSerialization JSONObjectWithData:data
- options:NSJSONReadingMutableContainers
- error:&serializationError];
- GTLR_DEBUG_ASSERT(result != nil, @"Copy failed due to deserialization: %@\nJSON: %@",
- serializationError,
- [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
- }
- }
- return result;
- }
|