Search code examples
objective-crecursionnsdictionarynsnull

Removing nulls from a JSON structure recursively


I'm frequently finding the need to cache data structures created by NSJSONSerialization to disk and as -writeToFile fails if there are nulls, I need a fix that works when the structure is unknown. This works, and direct mutation is allowed as the instances of NSMutableDictionary themselves are not being enumerated, but it feels a bit hacky.

Is this totally fine or is it absolutely necessary to recreate a new tree and return that?

- (void) removeNullsFromJSONTree:(id) branch
{
    if ([branch isKindOfClass:[NSMutableArray class]])
    {
        //Keep drilling to find the leaf dictionaries
        for (id childBranch in branch)
        {
            [self removeNullsFromJSONTree:childBranch];
        }
    }
    else if ([branch isKindOfClass:[NSMutableDictionary class]])
    {
        const id nul = [NSNull null];
        const NSString *empty = @"";
        for(NSString *key in [branch allKeys])
        {
            const id object = [branch objectForKey:key];
            if(object == nul)
            {
                [branch setObject:empty forKey:key];
            }
        }
    }
}

Solution

  • There's nothing wrong with your general approach. Since NSNull is a singleton, it's fine to look for it by pointer comparison.

    However, you're not recursing on the values in your dictionary. In general, those values might be arrays or dictionaries themselves. Perhaps in your specific case you know they're not. But if they could be, you need to perform removeNullsFromJSONTree: on each value in the dictionary.

    You also don't look for NSNull in an array. Should you? It's trivial to handle:

    [branch removeObject:[NSNull null]];
    

    The removeObject: method removes all instances of the argument.

    Personally I don't like testing object classes explicitly when I can use categories to let the message sending system do it for me. So instead I might define a category on NSObject like this:

    // NSObject+KezRemoveNulls.h
    
    @interface NSObject (KezRemoveNulls)
    
    - (void)Kez_removeNulls;
    
    @end
    

    I would write a default do-nothing implementation for NSObject, and override it for NSMutableArray and NSMutableDictionary:

    // NSObject+KezRemoveNulls.m
    
    #import "NSObject+KezRemoveNulls.h"
    
    @implementation NSObject (KezRemoveNulls)
    
    - (void)Kez_removeNulls {
        // nothing to do
    }
    
    @end
    
    @implementation NSMutableArray (KezRemoveNulls)
    
    - (void)Kez_removeNulls {
        [self removeObject:[NSNull null]];
        for (NSObject *child in self) {
            [child Kez_removeNulls];
        }
    }
    
    @end
    
    @implementation NSMutableDictionary (KezRemoveNulls)
    
    - (void)Kez_removeNulls {
        NSNull *null = [NSNull null];
        for (NSObject *key in self.allKeys) {
            NSObject *value = self[key];
            if (value == null) {
                [self removeObjectForKey:key];
            } else {
                [value Kez_removeNulls];
            }
        }
    }
    
    @end
    

    Note that all of the implementation code is still in one file.

    Now I could say this:

    id rootObject = [NSJSONSerialization JSONObjectWithData:...];
    [rootObject Kez_removeNulls];