2012-03-28 16 views
14

Aquí está el código:NSJSONSerialization no crear contenedores mutables

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

Como se puede ver, estoy llamando exactamente el mismo método para analizar el JSON las dos veces, una con una lista vacía en el JSON, y luego una lista con un objeto adentro. Aquí está el resultado:

Is mutable? 0 
Is mutable? 1 

El problema es que el NSJSONSerialization no parece seguir la opción de crear contenedores mutables para las listas vacías. Me parece un error, pero tal vez solo estoy entendiendo mal las cosas.

¿Alguna idea?

+0

¿Estás seguro de que no están apareciendo como NSMutableDictionary? – Zalykr

+0

¿Cuál es el resultado cuando ejecuta 'po [[listOfObjects class] superclass]' en la consola de depuración? – warrenm

+1

Puedo confirmar este problema en Mac OS X 10.7: solo las matrices vacías parecen verse afectadas. Parece estar arreglado en 10.8. – blutfink

Respuesta

13

Esto funciona como se esperaba:

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

Aquí está la salida de la consola:

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 

EDITAR

Tenga en cuenta que si añadimos la siguiente línea en el código anterior ..

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

... obtenemos la siguiente salida en la consola:

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

... en caso de que no estaba claro que __NSArrayM es una subclase particular de NSMutableArray, demostrando así que el código de la OP, efectivamente, funciona como se espera (excepto por su declaración NSLog).

EDITAR

Ah, y por cierto, la siguiente línea de código ...

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

... se obtiene la siguiente salida de la consola:

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

EDIT (responder a la pregunta modificada)

Interesante ... parece un error. Dado el siguiente código:

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

Aquí está la salida de la consola: no parecen

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 

matrices tan vacío y diccionarios creados de este modo de comportarse como se esperaba.

+0

Sin embargo, la [clase de gastos] sigue siendo un NSArray, en lugar de una matriz mutable, ¿no? –

+0

No. Primero, no puede agregar un objeto a una instancia de 'NSArray'. En segundo lugar, si mira la salida de la consola, la matriz es en realidad una instancia de '__NSArrayM', que es una subclase privada de' NSMutableArray'. – jlehr

+0

Hola, gracias por la respuesta hasta ahora. He actualizado la pregunta, sin embargo, pensé que no estaba explicando el problema correctamente. ¿Puedes volver a verificar? –

1

Otros también toman esto para un error, ver

  1. https://github.com/couchbaselabs/TouchDB-iOS/issues/44 o
  2. https://github.com/johnlabarge/jlbiosutils/blob/master/jlbiosutils/DynamicProperties.m, por ejemplo.

En este último caso, puede ver una solución completa también para los diccionarios vacíos (consulte el método DynamicGetter(...)).

+0

Cualquier cosa que Jens Alfke considere un error es un error para mí. Gracias por los enlaces, terminé arreglando esto de la misma manera. –

7

aquí está mi solución para este problema:

#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 

Así que me llaman:

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

En lugar de la habitual:

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

genial! debería ser la respuesta aceptada ... –

1

Aquí es lo que hago:

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