GBA002/Pods/GoogleAPIClientForREST/Source/Objects/GTLRObject.m
Riley Testut 6cca0f244f Replaces frameworks with static libraries
As of iOS 13.3.1, apps installed with free developer accounts that contain embedded frameworks fail to launch. To work around this, we now link all dependencies via Cocoapods as static libraries.
2020-02-03 19:28:23 -08:00

769 lines
26 KiB
Objective-C

/* 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.
*/
#if !__has_feature(objc_arc)
#error "This file needs to be compiled with ARC enabled."
#endif
#include <objc/runtime.h>
#import "GTLRObject.h"
#import "GTLRRuntimeCommon.h"
#import "GTLRUtilities.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 {
GTLR_ASSERT(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;
}