NSJSONSerialization не создает изменяемые контейнеры

Здесь код:

NSError *parseError;
NSMutableArray *listOfObjects = [NSJSONSerialization JSONObjectWithData:[@"[]" dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&parseError];
NSLog(@"Is mutable? %li", [listOfObjects isKindOfClass:[NSMutableArray class]]);

listOfObjects = [NSJSONSerialization JSONObjectWithData:[@"[[],{}]" dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&parseError];
NSLog(@"Is mutable? %li", [listOfObjects isKindOfClass:[NSMutableArray class]]);

Как вы можете видеть, я вызываю точно такой же метод для разбора JSON оба раза, один с пустым списком в JSON, а затем список с объектом внутри. Здесь результат:

Is mutable? 0
Is mutable? 1 

Проблема заключается в том, что NSJSONSerialization, похоже, не соответствует опции создания изменяемых контейнеров для пустых списков. Кажется, это ошибка, но, может быть, я просто неправильно понимаю вещи.

Любые идеи?

Ответы

Ответ 1

Это работает так, как ожидалось:

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]);

Здесь вывод консоли:

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

ИЗМЕНИТЬ

Обратите внимание, что если мы добавим следующую строку в код выше...

NSLog(@"%@", [[dict objectForKey:@"objs"] superclass]);

... мы получаем следующий вывод на консоли:

2012-03-28 18:09:53.770 ExampleRunner[42830:707] NSMutableArray

... на всякий случай неясно, что __NSArrayM является частным подклассом NSMutableArray, тем самым доказывая, что OP-код действительно работает как ожидалось (кроме его оператора NSLog).

ИЗМЕНИТЬ

О, и, кстати, следующая строка кода...

NSLog(@"%d", [[dict objectForKey:@"objs"] isKindOfClass:[NSMutableArray class]]);

... выводит следующий вывод консоли:

2012-03-28 18:19:19.721 ExampleRunner[42886:707] 1

EDIT (ответ на измененный вопрос)

Интересный... выглядит как ошибка. Учитывая следующий код:

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);

Здесь вывод консоли:

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

Таким образом, пустые массивы и словари, созданные таким образом, не ведут себя так, как ожидалось.

Ответ 2

Вот мой способ решения этой проблемы:

#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 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 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

Итак, я вызываю:

NSDictionary *object = [NSJSONSerialization JSONObjectWithDataFixed:jsonData options:NSJSONReadingMutableContainers error:&err];

Вместо обычного:

NSDictionary *object = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];

Ответ 4

Вот что я делаю:

BOOL needsWorkaround = YES;
if (needsWorkaround)
{
    NSMutableDictionary* appState2 = 
        (__bridge_transfer NSMutableDictionary*) 
        CFPropertyListCreateDeepCopy (
            kCFAllocatorDefault, (__bridge void*)appState,
            kCFPropertyListMutableContainersAndLeaves
        );
    appState = appState2;
}