ios - 规范化 JSON,使等效对象具有相同的哈希值

标签 ios json foundation nsjsonserialization

我将 JSON 对象存储在数据库中。这些对象中的许多(也许是大多数)都是重复的,因此我想将它们键入 SHA 散列之类的对象,以避免创建不必要的额外记录。

问题是,在我想将它们写入数据库时​​,我不再拥有 JSON 字节——只有 NSJSONSerialization 返回的 Foundation 对象。因为 NSDictionary 不对键顺序做任何保证(即使它做了,我也不确定我从中获取数据的服务器),我不能确定 NSJSONSerialization 会在我每次调用它时以相同的顺序输出每个对象的字段。这意味着同一个对象可能有不同的摘要,这让我节省空间的尝试落空了。

是否有一个 Objective-C JSON 库确实总是为等效对象编写完全相同的 JSON,大概是在编写键之前对键进行排序?我的目标是 iOS 7,但这可能是基础级别的问题。

最佳答案

我没有尝试编写自己的 JSON 序列化程序,而是决定通过一些代理技巧来欺骗 Apple 做我想做的事情。

用法:

NSData * JSONData = [NSJSONSerialization dataWithJSONObject:[jsonObject objectWithSortedKeys] options:0 error:&error];

标题:

#import <Foundation/Foundation.h>

@interface NSObject (sortedKeys)

/// Returns a proxy for the object in which all dictionary keys, including those of child objects at any level, will always be enumerated in sorted order.
- (id)objectWithSortedKeys;

@end

代码:

#import "NSObject+sortedKeys.h"

/// A CbxSortedKeyWrapper intercepts calls to methods like -allKeys, -objectEnumerator, -enumerateKeysAndObjectsUsingBlock:, etc. and makes them enumerate a sorted array of keys, thus ensuring that keys are enumerated in a stable order. It also replaces objects returned by any other methods (including, say, -objectForKey: or -objectAtIndex:) with wrapped versions of those objects, thereby ensuring that child objects are similarly sorted. There are a lot of flaws in this approach, but it works well enough for NSJSONSerialization.
@interface CbxSortedKeyWrapper: NSProxy

+ (id)sortedKeyWrapperForObject:(id)object;

@end

@implementation NSObject (sortedKeys)

- (id)objectWithSortedKeys {
    return [CbxSortedKeyWrapper sortedKeyWrapperForObject:self];
}

@end

@implementation CbxSortedKeyWrapper {
    id _representedObject;
    NSArray * _keys;
}


+ (id)sortedKeyWrapperForObject:(id)object {
    if(!object) {
        return nil;
    }

    CbxSortedKeyWrapper * wrapper = [self alloc];
    wrapper->_representedObject = [object copy];

    if([wrapper->_representedObject respondsToSelector:@selector(allKeys)]) {
        wrapper->_keys = [[wrapper->_representedObject allKeys] sortedArrayUsingSelector:@selector(compare:)];
    }

    return wrapper;
}

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {
    return [_representedObject methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation*)invocation {
    [invocation invokeWithTarget:_representedObject];

    BOOL returnsObject = invocation.methodSignature.methodReturnType[0] == '@';

    if(returnsObject) {
        __unsafe_unretained id out = nil;
        [invocation getReturnValue:&out];

        __unsafe_unretained id wrapper = [CbxSortedKeyWrapper sortedKeyWrapperForObject:out];
        [invocation setReturnValue:&wrapper];
    }
}

- (NSEnumerator *)keyEnumerator {
    return [_keys objectEnumerator];
}

- (NSEnumerator *)objectEnumerator {
    if(_keys) {
        return [[self allValues] objectEnumerator];
    }
    else {
        return [CbxSortedKeyWrapper sortedKeyWrapperForObject:[_representedObject objectEnumerator]];
    }
}

- (NSArray *)allKeys {
    return _keys;
}

- (NSArray *)allValues {
    return [CbxSortedKeyWrapper sortedKeyWrapperForObject:[_representedObject objectsForKeys:_keys notFoundMarker:[NSNull null]]];
}

- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block {
    [_keys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) {
        id obj = [CbxSortedKeyWrapper sortedKeyWrapperForObject:_representedObject[key]];
        block(key, obj, stop);
    }];
}

- (void)enumerateKeysAndObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id key, id obj, BOOL *stop))block {
    [_keys enumerateObjectsWithOptions:opts usingBlock:^(id key, NSUInteger idx, BOOL *stop) {
        id obj = [CbxSortedKeyWrapper sortedKeyWrapperForObject:_representedObject[key]];
        block(key, obj, stop);
    }];
}

- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block {
    [_representedObject enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
        block([CbxSortedKeyWrapper sortedKeyWrapperForObject:obj], idx, stop);
    }];
}

- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block {
    [_representedObject enumerateObjectsWithOptions:opts usingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
        block([CbxSortedKeyWrapper sortedKeyWrapperForObject:obj], idx, stop);
    }];
}

- (void)enumerateObjectsAtIndexes:(NSIndexSet *)indexSet options:(NSEnumerationOptions)opts usingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block {
    [_representedObject enumerateObjectsAtIndexes:indexSet options:opts usingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
        block([CbxSortedKeyWrapper sortedKeyWrapperForObject:obj], idx, stop);
    }];
}

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len {
    NSUInteger count = [_keys countByEnumeratingWithState:state objects:stackbuf count:len];
    for(NSUInteger i = 0; i < count; i++) {
        stackbuf[i] = [CbxSortedKeyWrapper sortedKeyWrapperForObject:stackbuf[i]];
    }
    return count;
}

@end

关于ios - 规范化 JSON,使等效对象具有相同的哈希值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21672923/

相关文章:

来自json的url中的php数组

objective-c - 从库中调用类别方法时出现 "unrecognized selector sent to class"

ios - 委托(delegate)返回 nil 且未被调用

ios - AVAudioEngine 求歌曲时间

ios - 如何为动画添加完成?

ios - 从 uiviewcontroller 操作 uicollectionviewcell 内的 uicollectionview

javascript - 通过另一个下拉菜单过滤下拉菜单

javascript - 将 $.ajax() 中的回调响应视为 JSON

ios - 检查数组是否包含两个对象

cocoa - 核心基础与基础