【问题标题】:Removing nulls from a JSON structure recursively递归地从 JSON 结构中删除空值
【发布时间】:2013-04-25 03:21:31
【问题描述】:

我经常发现需要将NSJSONSerialization 创建的数据结构缓存到磁盘,如果有空值,-writeToFile 会失败,我需要一个在结构未知时有效的修复程序。 这行得通,并且允许直接变异,因为 NSMutableDictionary 本身的实例没有被枚举,但感觉有点 hacky。

这完全没问题,还是绝对有必要重新创建一棵新树并返回它?

- (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];
            }
        }
    }
}

【问题讨论】:

  • if([object isKindOfClass:[NSNull null]]) 试试这样
  • 是的,它最初是使用isKindOfClass 进行测试的,但正如关于删除空值(我似乎找不到)的类似 SO 问题中所述,比较指向常量的指针更有效。
  • 我创建了一个可以做到这一点的类别。你可以在这里找到它github.com/bismasaeed00/NullReplacer

标签: objective-c recursion nsdictionary nsnull


【解决方案1】:

您的一般方法没有任何问题。由于NSNull是单例,所以可以通过指针比较来查找。

但是,您不会递归字典中的值。通常,这些值可能是数组或字典本身。也许在您的具体情况下,您知道他们不是。但如果可以,您需要对字典中的每个值执行removeNullsFromJSONTree:

您也不必在数组中查找NSNull。你应该?处理起来很简单:

[branch removeObject:[NSNull null]];

removeObject: 方法删除参数的所有实例。

当我可以使用类别让消息发送系统为我完成时,我个人不喜欢明确地测试对象类。因此,我可能会像这样在NSObject 上定义一个类别:

// NSObject+KezRemoveNulls.h

@interface NSObject (KezRemoveNulls)

- (void)Kez_removeNulls;

@end

我将为NSObject 编写一个默认的无操作实现,并为NSMutableArrayNSMutableDictionary 覆盖它:

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

请注意,所有实现代码仍在一个文件中。

现在我可以这样说:

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

【讨论】:

  • 我只是想知道Avoid Category Method Name Clashes是否适用于这里:"如果一个类别中声明的方法的名称与原始类中的方法相同,或者另一个类别中的方法同一个类(甚至是超类),行为未定义...".
  • 我相信在这种情况下没问题,因为我为超类和子类定义了相同的类别 (KezRemoveNulls)。您的报价说“或 另一个 类别中的方法”。
  • 你说得对,我忽略了那部分,感谢您的反馈! - (现在我想起来了:我对 NSManagedObject 上的类别做了同样的事情 :-)
  • 实际上,我很确定这是可行的,并且曾经在一些文档中得到更好的解释,Apple 不幸地从他们的网站上删除了。 (See this answer.)。如果有机会,在 WWDC 上向 Greg Parker 提问是一个合理的问题。
  • 我很幸运能在 2 分钟内拿到一张票 :-) 但我必须承认我不认识 Greg Parker。
【解决方案2】:

这是我用来清理 JSON 调用的代码,似乎运行良好,但由于涉及一些处理开销,我实际上只在无法在服务器上进行 null 处理的情况下使用它。 NSNull 崩溃是我们最大的应用崩溃问题。

+ (id)cleanJsonToObject:(id)data {
    NSError* error;
    if (data == (id)[NSNull null]){
        return [[NSObject alloc] init];
    }
    id jsonObject;
    if ([data isKindOfClass:[NSData class]]){
        jsonObject = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
    } else {
        jsonObject = data;
    }
    if ([jsonObject isKindOfClass:[NSArray class]]) {
        NSMutableArray *array = [jsonObject mutableCopy];
        for (int i = array.count-1; i >= 0; i--) {
            id a = array[i];
            if (a == (id)[NSNull null]){
                [array removeObjectAtIndex:i];
            } else {
                array[i] = [self cleanJsonToObject:a];
            }
        }
        return array;
    } else if ([jsonObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *dictionary = [jsonObject mutableCopy];
        for(NSString *key in [dictionary allKeys]) {
            id d = dictionary[key];
            if (d == (id)[NSNull null]){
                dictionary[key] = @"";
            } else {
                dictionary[key] = [self cleanJsonToObject:d];
            }
        }
        return dictionary;
    } else {
        return jsonObject;
    }
}

您通过传递通过 NSURLConnection 检索到的 NSData 来调用它。

NSArray *uableData = [utility cleanJsonToObject:data];

NSDictionary *uableData = [utility cleanJsonToObject:data];

【讨论】:

  • 太棒了!我正在寻找类似的东西。您节省了我编写自己的方法的时间。谢谢@Travis :)
【解决方案3】:

@Travis M. 答案的 Swift 4 版本;

class func removeNullFromJSONData(_ JSONData: Any) -> Any {
    if JSONData as? NSNull != nil {
        return JSONData
    }

    var JSONObject: Any!

    if JSONData as? NSData != nil {
        JSONObject = try! JSONSerialization.data(withJSONObject: JSONData, options: JSONSerialization.WritingOptions.prettyPrinted)
    }
    else {
        JSONObject = JSONData
    }

    if JSONObject as? NSArray != nil {
        let mutableArray: NSMutableArray = NSMutableArray(array: JSONObject as! [Any], copyItems: true)
        let indexesToRemove: NSMutableIndexSet = NSMutableIndexSet()

        for index in 0 ..< mutableArray.count {
            let indexObject: Any = mutableArray[index]

            if indexObject as? NSNull != nil {
                indexesToRemove.add(index)
            }
            else {
                mutableArray.replaceObject(at: index, with: removeNullFromJSONData(indexObject))
            }
        }

        mutableArray.removeObjects(at: indexesToRemove as IndexSet)

        return mutableArray;
    }
    else if JSONObject as? NSDictionary != nil {
        let mutableDictionary: NSMutableDictionary = NSMutableDictionary(dictionary: JSONObject as! [AnyHashable : Any], copyItems: true)

        for key in mutableDictionary.allKeys {
            let indexObject: Any = mutableDictionary[key] as Any

            if indexObject as? NSNull != nil {
                mutableDictionary.removeObject(forKey: key)
            }
            else {
                mutableDictionary.setObject(removeNullFromJSONData(indexObject), forKey: key as! NSCopying)
            }
        }

        return mutableDictionary
    }
    else {
        return JSONObject
    }
}

【讨论】:

    【解决方案4】:
    + (id)getObjectWithoutNullsForObject:(id)object
    {
        id objectWithoutNulls;
    
        if ([object isKindOfClass:[NSDictionary class]])
        {
            NSMutableDictionary *dictionary = ((NSDictionary *)object).mutableCopy;
    
            [dictionary removeObjectsForKeys:[dictionary allKeysForObject:[NSNull null]]];
    
            for (NSString *key in dictionary.allKeys)
            {
                dictionary[key] = [self getObjectWithoutNullsForObject:dictionary[key]];
            }
    
            objectWithoutNulls = dictionary;
        }
        else if ([object isKindOfClass:[NSArray class]])
        {
            NSMutableArray *array = ((NSArray *)object).mutableCopy;
    
            [array removeObject:[NSNull null]];
    
            for (NSUInteger index = 0; index < array.count; index++)
            {
                array[index] = [self getObjectWithoutNullsForObject:array[index]];
            }
    
            objectWithoutNulls = array;
        }
        else if ([object isKindOfClass:[NSNull class]])
        {
            objectWithoutNulls = Nil;
        }
        else
        {
            objectWithoutNulls = object;
        }
    
        return objectWithoutNulls;
    }
    

    【讨论】:

    • @Huperniketes -> 2) 将数组和字典元素设置为 Nil 默认会引发异常。 -> 如果你运行上面的代码,这永远不会发生。
    • @Huperniketes -> 3) 索引数组非常低效。请改用快速枚举。 -> 如果使用快速枚举,则无法将对象重新分配给实际数组。
    • 1) 虽然您对递归的理解已经足够好,但 ObjC 是一种面向对象的语言,您无法使用多态性从代码中隐藏其他类的细节。将 -isKindOfClass: 的使用替换为各自类中的适当方法。 -> 并不总是需要使用多态性。
    猜你喜欢
    • 2012-09-13
    • 1970-01-01
    • 2015-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-01
    • 2011-03-05
    • 2023-02-23
    相关资源
    最近更新 更多