Canonicalize JSON使等效对象具有相同的散列

时间:2014-02-10 09:10:48

标签: ios json foundation nsjsonserialization

我将JSON对象存储在数据库中。许多 - 也许大多数这些对象都是重复的,所以我想将它们键入类似SHA哈希的东西,以避免创建不必要的额外记录。

问题是,在我想将它们写入数据库时​​,我不再拥有JSON字节 - 只有NSJSONSerialization返回的Foundation对象。因为NSDictionary没有对密钥顺序做出任何保证(即使它确实如此,我也不确定我从哪个服务器获取数据),我无法确定{{1}每次调用它时,都会以相同的顺序输出每个对象的字段。这意味着同一个对象可以有不同的摘要,从而打败了我节省空间的尝试。

是否存在一个Objective-C JSON库 总是为等效对象编写完全相同的JSON,可能是在写入之前对键进行排序?我的目标是iOS 7,但这可能是基金会关注的问题。

2 个答案:

答案 0 :(得分:0)

首先,有效的JSON(文本)也不适合生成哈希:对于特定对象,可以有许多和有效形式的JSON来表示此对象:

  • JSON基本上是“文本”,其字符编码是Unicode。 Unicode有五种不同的 unicode方案:它可以是UTF-8,UTF-16BE,UTF-16LE,UTF-32BE或UTF-32LE。每个方案都会产生不同的哈希值,即使对象是相同的。

  • JSON可能包含空格和制表符(又名“漂亮打印”)。

  • 然后,JSON中的任何字符都可以选择表示为转义的unicode。 “solidus”字符/可能会或可能不会被转义。

  • 此外,未指定JSON对象中元素的顺序。最后,JSON-Object中重复键的行为也是未定义的。

因此,对于任何唯一对象,可能存在多个有效的JSON(文本)表示,这使得不适合创建哈希。

解决方案需要定义您自己的JSON解析器和序列化程序,它具有使生成的表示(无论它实际上是什么)适合散列的属性。

显然,使用“普通”JSON解析器/序列化器就足够了:给定一个有效的JSON,我们将创建一个表示,然后通过设置选项将其序列化为“规范”JSON,生成一种特殊形式的JSON,其中钥匙将被订购。

但是,这假设您总是使用完全相同的解析器/序列化器来生成数据库生命周期的哈希值。这意味着,可能未记录的内部和实现细节不得更改,从而保证JSON的生成和有效变体始终完全相同(参见上面如何表示JSON)。如果某些实现细节会发生变化,例如较新的版本现在会转义为“solidus”字符,那么您的数据库将会中断。

不幸的是,NSJSONSerialization缺少那种“文档”,也没有选项来设置这些属性(例如键的排序)来创建这样一个“特殊”的JSON表示,它可以适合创建一个JSON对象的相应哈希。

您可以在第三方库之后搜索,该库提供源代码,您可以完全控制生成的适用于散列的JSON变体。我强烈反对尝试实现你自己的解析器/序列化器 - 因为它不像第一眼看上去那么容易。

为了解决您的问题(“Canonicalize JSON”),您甚至不需要生成 Foundation 表示的JSON解析器/序列化器: any 表示形式就足够了(例如C ++容器或任何自定义容器),只要它生成符合您要求的规范JSON (形成任何有效的JSON)即可。

我很确定有一些第三方库适合解决您的问题。我自己用一个基于C ++实现的Objective-C API实现了一个JSON解析器/序列化器。它很可能是您问题的解决方案,因为它有很多选项来控制输出(JPJSONWriter选项:JPJsonWriterSortKeysJPJsonWriterEscapeSolidus)。但是,这个库并不容易应用,因为它的源代码非常繁重(Objective-C API,C ++高级模板,并针对性能和低内存占用进行了优化,增加了大量源代码)。

如果有帮助:JPJson(我的尝试)

JPJson分离了解析和相关“语义动作”的概念。例如,“语义动作”是“基础表示生成器”。也就是说,你可以实现一个“HashGenerator”类,它直接从接收到的输入创建一个哈希,而不会叠加表示。

可能是Andrii Mamchur的JSON库:jsonlite,其中JsonLiteSerializer方法serializeDictionary:可以很容易地修改,以便在生成输出之前对键进行排序。

还有几个图书馆。

答案 1 :(得分:0)

我没有尝试编写自己的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