Here\'s the code:
NSError *parseError;
NSMutableArray *listOfObjects = [NSJSONSerialization JSONObjectWithData:[@\"[]\" dataUsingEncoding:NSUTF8StringEncodin
Others also take this for a bug, see
In the latter case you can see complete workaround also for empty dictionaries (see DynamicGetter(...)
method).
Here's my workaround for this problem:
#import "NSJSONSerialization+MutableBugFix.h"
@implementation NSJSONSerialization (NSJSONSerialization_MutableBugFix)
+ (id)JSONObjectWithDataFixed:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error {
id object = [NSJSONSerialization JSONObjectWithData:data options:opt error:error];
if (opt & NSJSONReadingMutableContainers) {
return [self JSONMutableFixObject:object];
}
return object;
}
+ (id)JSONMutableFixObject:(id)object {
if ([object isKindOfClass:[NSDictionary class]]) {
// NSJSONSerialization creates an immutable container if it's empty (boo!!)
if ([object count] == 0) {
object = [object mutableCopy];
}
for (NSString *key in [object allKeys]) {
[object setObject:[self JSONMutableFixObject:[object objectForKey:key]] forKey:key];
}
} else if ([object isKindOfClass:[NSArray class]]) {
// NSJSONSerialization creates an immutable container if it's empty (boo!!)
if (![object count] == 0) {
object = [object mutableCopy];
}
for (NSUInteger i = 0; i < [object count]; ++i) {
[object replaceObjectAtIndex:i withObject:[self JSONMutableFixObject:[object objectAtIndex:i]]];
}
}
return object;
}
@end
So I call:
NSDictionary *object = [NSJSONSerialization JSONObjectWithDataFixed:jsonData options:NSJSONReadingMutableContainers error:&err];
Instead of the usual:
NSDictionary *object = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
Here is what I do:
BOOL needsWorkaround = YES;
if (needsWorkaround)
{
NSMutableDictionary* appState2 =
(__bridge_transfer NSMutableDictionary*)
CFPropertyListCreateDeepCopy (
kCFAllocatorDefault, (__bridge void*)appState,
kCFPropertyListMutableContainersAndLeaves
);
appState = appState2;
}
This works just as expected:
NSString *s = @"{ \"objs\": [ \"a\", \"b\" ] }";
NSData *d = [NSData dataWithBytes:[s UTF8String] length:[s length]];
id dict = [NSJSONSerialization JSONObjectWithData:d options:NSJSONReadingMutableContainers error:NULL];
NSLog(@"%@", dict);
[[dict objectForKey:@"objs"] addObject:@"c"];
NSLog(@"%@", dict);
NSLog(@"%@", [[dict objectForKey:@"objs"] class]);
Here's the console output:
2012-03-28 13:49:46.224 ExampleRunner[42526:707] {
objs = (
a,
b
);
}
2012-03-28 13:49:46.225 ExampleRunner[42526:707] {
objs = (
a,
b,
c
);
}
2012-03-28 13:49:46.225 ExampleRunner[42526:707] __NSArrayM
EDIT
Note that if we append the following line to the code above...
NSLog(@"%@", [[dict objectForKey:@"objs"] superclass]);
...we get the following output on the console:
2012-03-28 18:09:53.770 ExampleRunner[42830:707] NSMutableArray
...just in case it wasn't clear that __NSArrayM
is a private subclass of NSMutableArray
, thus proving that the OP's code did indeed work as expected (except for his NSLog
statement).
EDIT
Oh, and by the way, the following line of code...
NSLog(@"%d", [[dict objectForKey:@"objs"] isKindOfClass:[NSMutableArray class]]);
...results in the following console output:
2012-03-28 18:19:19.721 ExampleRunner[42886:707] 1
EDIT (responding to changed question)
Interesting...looks like a bug. Given the following code:
NSData *dictData2 = [@"{ \"foo\": \"bar\" }" dataUsingEncoding:NSUTF8StringEncoding];
id dict2 = [NSJSONSerialization JSONObjectWithData:dictData2 options:NSJSONReadingMutableContainers error:NULL];
NSLog(@"%@", [dict2 class]);
NSLog(@"%@", [dict2 superclass]);
NSLog(@"%d", [dict2 isKindOfClass:[NSMutableDictionary class]]);
// This works...
[dict2 setObject:@"quux" forKey:@"baz"];
NSLog(@"%@", dict2);
NSData *dictData = [@"{}" dataUsingEncoding:NSUTF8StringEncoding];
id emptyDict = [NSJSONSerialization JSONObjectWithData:dictData options:NSJSONReadingMutableContainers error:NULL];
NSLog(@"%@", [emptyDict class]);
NSLog(@"%@", [emptyDict superclass]);
NSLog(@"%d", [emptyDict isKindOfClass:[NSMutableDictionary class]]);
//...but this fails:
[emptyDict setObject:@"quux" forKey:@"baz"];
NSLog(@"%@", emptyDict);
Here's the console output:
2012-03-29 09:40:52.781 ExampleRunner[43816:707] NSMutableDictionary
2012-03-29 09:40:52.782 ExampleRunner[43816:707] 1
2012-03-29 09:40:52.782 ExampleRunner[43816:707] __NSCFDictionary
2012-03-29 09:40:52.782 ExampleRunner[43816:707] NSMutableDictionary
2012-03-29 09:40:52.783 ExampleRunner[43816:707] 1
2012-03-29 09:40:52.783 ExampleRunner[43816:707] {
baz = quux;
foo = bar;
}
2012-03-29 09:40:52.784 ExampleRunner[43816:707] __NSCFDictionary
2012-03-29 09:40:52.784 ExampleRunner[43816:707] NSMutableDictionary
2012-03-29 09:40:52.784 ExampleRunner[43816:707] 1
2012-03-29 09:40:52.785 ExampleRunner[43816:707] NSException: -[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object
So empty arrays and dictionaries created this way don't seem to behave as expected.