0

I'm trying to use the writeToFile method of an NSArray of NSDictionaries which doesn't allow nulls. I figured just loop through and find them and replace with empty NSString's but I can't get it to work. This is what I'm trying.

EDIT: The NSArrays and NSDictionaries are created by NSJSONSerialization.

for (NSMutableDictionary *mainDict in mainArray)
{
    for(NSMutableDictionary *subDict in mainDict)
    {
        for(NSString *key in subDict)
        {                
            id value = [subDict objectForKey:key];

            if (value == [NSNull null])
            {
                [subDict setObject:@"" forKey:key];
            }
        }
    }
}

But this throws an exception. *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'

But as you can see all NSDictionaries are mutable.

What's up? Is there a better way to do this?

EDIT: Moved new version to a separate answer.

TijuanaKez
  • 1,472
  • 3
  • 20
  • 28

2 Answers2

5

How do you know that the dictionaries are mutable? It looks like they’re not. Typing them as NSMutableDictionary during the enumeration doesn’t make them mutable. You could walk over the dictionary and move the keys into a new, mutable one, filtering the nulls.

Also, if your dictionary comes from NSJSONSerialization, take a look at NSJSONReadingOptions, there’s an option to use mutable containers for the deserialized structure. That’s not always a good solution, but it’s worth checking out.

As for mutating a dictionary while enumerating it: you can’t change the keys, since that could easily throw off the enumeration code. But I don’t see why you couldn’t change the key values, which is all you’re after. This code works:

NSMutableDictionary *dict = [@{@"foo" : @"bar"} mutableCopy];
for (NSString *key in dict) {
    dict[key] = @"baz";
}
zoul
  • 102,279
  • 44
  • 260
  • 354
  • 3
    Also, mutating a dictionary while enumerating is an error and throws an exception. –  Nov 17 '12 at 09:07
  • 1
    Yeah..... I would suggest creating a seperate NSDictionary and setting it to this dictionary he is enumerating after the enumeration – MCKapur Nov 17 '12 at 09:09
  • Ok thanks. It's hard to know the structure of the tree as it's created by NSJSONSerialization. I've created a new version with mutable copies. Am I on the right track here or missing the point? – TijuanaKez Nov 18 '12 at 09:24
  • Ok that doesn't work because of what H2CO3 pointed out. I don't quite understand why that's not allowed but what would be the correct way? – TijuanaKez Nov 18 '12 at 09:31
  • I’ve expanded the answer (too much text to fit in a comment), hope that helps. – zoul Nov 18 '12 at 09:42
  • Note that `NSJSONSerialization` has an option `NSJSONReadingMutableContainers` to create arrays and dictionaries as mutable objects. – Martin R Nov 18 '12 at 09:59
  • Ok great, NSJSONReadingMutableContainers makes the first version valid except the setting of values. I've not encountered this syntax before though '@{@"foo" : @"bar"}'??? – TijuanaKez Nov 18 '12 at 11:53
  • That’s [collection literals](http://clang.llvm.org/docs/ObjectiveCLiterals.html), a recent addition to the language. – zoul Nov 18 '12 at 12:04
  • OK thanks so the first line is equivalent to 'NSMutableDictionary *dict = [[NSDictionary dictionaryWithObjectsAndKeys:@"foo", @"bar, nil] mutableCopy];'. Also I realised the problem with that new version is I should be enumerating 'dict' while mutating 'newDict'. – TijuanaKez Nov 18 '12 at 21:22
0

Or rebuilding the whole tree works.

NSMutableArray *newMainArray = [[NSMutableArray alloc] init];
for (NSArray *subArray in self.mainArray)
{
    NSMutableArray *newSubArray = [[NSMutableArray alloc] init];
    for(NSDictionary *dict in subArray)
    {
        NSMutableDictionary *newDict = [[NSMutableDictionary alloc] init];
        for(NSString *key in dict)
        {              
            id value = [dict objectForKey:key];
            if (value == [NSNull null])
            {
                [newDict setObject:@"" forKey:key];
            } else {
                [newDict setObject:value forKey:key];
            }
        }
        [newSubArray addObject:newdict];
        [newDict release];
    }
    [newMainArray addObject:newSubArray];
    [newSubArray release];
}
[self.mainArray release];     <----  ????
self.mainArray = newMainArray;
TijuanaKez
  • 1,472
  • 3
  • 20
  • 28
  • There is one more problem with this code. If I release self.mainArray before pointing it to newMainArray it throws an exception. The property is defined as '@property (nonatomic, retain) NSMutableArray *mainArray;'. Can I just reassign it without releasing first? – TijuanaKez Nov 18 '12 at 22:01