我将JSON对象存储在数据库中。许多 - 也许大多数这些对象都是重复的,所以我想将它们键入类似SHA哈希的东西,以避免创建不必要的额外记录。
问题是,在我想将它们写入数据库时,我不再拥有JSON字节 - 只有NSJSONSerialization
返回的Foundation对象。因为NSDictionary
没有对密钥顺序做出任何保证(即使它确实如此,我也不确定我从哪个服务器获取数据),我无法确定{{1}每次调用它时,都会以相同的顺序输出每个对象的字段。这意味着同一个对象可以有不同的摘要,从而打败了我节省空间的尝试。
是否存在一个Objective-C JSON库 总是为等效对象编写完全相同的JSON,可能是在写入之前对键进行排序?我的目标是iOS 7,但这可能是基金会关注的问题。
答案 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
选项:JPJsonWriterSortKeys
,JPJsonWriterEscapeSolidus
)。但是,这个库并不容易应用,因为它的源代码非常繁重(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