NSJSONSerialization 无法解析有效的 JSON - “垃圾结束”

Posted

技术标签:

【中文标题】NSJSONSerialization 无法解析有效的 JSON - “垃圾结束”【英文标题】:NSJSONSerialization Fails to Parse Valid JSON - "Garbage at End" 【发布时间】:2014-04-27 05:59:32 【问题描述】:

我的 ios 程序正在接收 JSON 数据并尝试解析它,但由于某种我无法确定的原因总是失败。多个线程几乎同时调用此函数。奇怪的是,这只是在我切换到使用 GCDAsyncSocket 后才开始发生的。下面是接收和解析数据的相关代码:

// Called whenever I want my program to receive null-terminated data from the server:
[socket readDataToData:[NSData dataWithBytes:"\0" length:1] withTimeout:10 tag:0];

- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag // a GCDAsyncSocket delegate method
    [self didReceiveNetworkData:data];


- (void)didReceiveNetworkData: (NSData*) data
    if (TESTING) NSLog(@"Received network data of length %lu===\n%@\n===", (unsigned long) data.length, [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding]);
    NSError* error;
    NSDictionary* json = [NSJSONSerialization
                          JSONObjectWithData: data
                          options:kNilOptions
                          error:&error];
    if (!json)
        NSLog(@"Got an error parsing received JSON data: %@", error);
        return;
    
    // Then I handle the dictionary (omitted code)…

日志和断点调试器说这是接收到的数据:


"responseType": -1

确切地说,它的字节是“\n\t\"responseType\":\t-1\n\0”。一旦 JSONObjectWithData 函数运行,我就会得到这个“垃圾结束”错误:

Got an error parsing received JSON data: Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Garbage at end.) UserInfo=0x10b203800 NSDebugDescription=Garbage at end.

根据在线 JSON 测试人员和我自己在单独项目中的简单测试,这是有效的 JSON。那么为什么我会收到错误消息?我认为它可能是在抱怨空终止符之后的额外字节,所以我尝试通过制作一个 NSString 然后将其转回 NSData 来修剪它,但这并没有帮助:

NSDictionary* json = [NSJSONSerialization
                      JSONObjectWithData: [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] dataUsingEncoding: NSUTF8StringEncoding]
                      options:kNilOptions
                      error:&error];

因为使用 GCDAsyncSocket 是这个问题的始作俑者,我想知道它是否与多线程有关,但我真的想不出有什么理由这么重要。有什么想法吗?

附:我使用 GCDAsyncSocket 因为我不知道如何处理 Objective-C 中的低级套接字,所以我的原始代码有很多奇怪的错误。

【问题讨论】:

你能在一个一次只做一个请求的简单测试项目中复制这个问题吗? 我只是尝试编辑现有的,所以它只会从服务器查询一件事,因此只收到一条消息,我验证只有一个线程在调用它。问题仍然存在。但我应该早点尝试。 \0 最后是垃圾。正如它所说的那样。 JSON 不想要 NUL 终止的数据并且不接受它。它不允许在 CR、LF 和 TAB 之后的 任何 控制字符。绝对没有 NUL 字节。 【参考方案1】:

奇怪的是,这就是它所需要的!在 didReceiveNetworkData 函数的开始处:

//Yes, I know this wastefully allocates RAM, so I'll fix it later.
NSString* string = [[[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] stringByReplacingOccurrencesOfString:@"\t" withString:@""] stringByReplacingOccurrencesOfString:@"\0" withString:@""];
data = [string dataUsingEncoding:NSUTF8StringEncoding];

我删除了所有 '\t'(条目之前)和 '\0'(字符串末尾)字符。仅删除 '\0' xor '\t' 不起作用;它必须是两者。除非我遗漏了什么,否则 NSJSONSerialization 既关心制表符又关心尾随空字符似乎很奇怪!

顺便说一句,cJSON 对此进行了编码,因此这意味着 cJSON 和 NSJSONSerialization 并不严格一致。在线 JSON 测试人员也同意 cJSON 的编码方式。

无论如何我可能是错的,因为这在没有 GCDAsyncSocket 之前可以工作(尽管我仍然必须删除空字符,正如我现在记得的那样)。或者也许我的旧解决方案是以某种方式删除制表符。

【讨论】:

我已经尝试过这个解决方案。这在模拟器上运行良好,但在我的 iPhone 上运行时仍然出现错误。错误消息是“垃圾结束”。 谢谢哥们。对于从 C 库生成的 json,我遇到了同样的问题。【参考方案2】:

更新: 您可能在某种程度上遇到了转义字符的问题。我自己不是 JSON 专家,但我记得我曾经将一个 JSON 字符串添加到字典中,然后从该字典创建了一个 JSON。 JSON 字符串本身变成了转义符,因此在解析过程中它会保留一个字符串。我的感觉是您需要删除转义字符。

也许您有一些竞争条件,并且在 JSONParsing 正在进行时正在写入数据。确保将 NSData 而不是 NSMutableData 传递给执行序列化的方法以确保。 请记住,NSJSONSerialization 不是流式 JSON 解析器。它接受一个不可变的 JSON 对象并将其序列化为 Foundation 对象。如果您正在向 NSData 添加字节,NSJSONSerialization 将在某个点看到在该点不是有效 JSON(还)的字节流。

【讨论】:

它正在传入一个 NSData。为了更加确定,我尝试让接收委托函数首先将数据复制到一个不可变的 NSData 并在 JSON 解析后检查以确保数据仍然相同(它是)。顺便说一句,我已经编辑了 OP 以包含 JSON 文本的确切字节。 我将此标记为答案,因为第一段足够接近我的问题,但我已经在下面描述了确切的解决方案。 我可以确认,在我的情况下,只需调用 [myJsonString copy] 并使用副本即可解决问题。 NSJSonSerializer 对我的源字符串做了坏事(它在串行 GCD 队列中被快速调用了 3 次,因此这可能是相关的)【参考方案3】:

即使我两次请求同一个 JSON 文件,我也遇到了这个问题。我通过从我创建的 URL 类中自定义检索 JSON 来更改我的 iOS 应用程序发出 Web 请求的方式来解决它:

我的老方法:

//init the download JSON class
var downloadJSON = createDownloadJSONObject()

//Downloads and parses the file correctly
downloadJSON.downloadJSON(url: "http://www.example.com/jsonfile.json")

/** waits for the first JSON object to return the json file successfully */

//throws the garbage collection error even though requested file is the same
downloadJSON.downloadJSON(url: "http://www.example.com/jsonfile.json")

我只能想象问题出在我如何处理对象的清理。

工作方法:

//init the download JSON class
var downloadJSON = createDownloadJSONObject()

//Downloads and parses the file correctly
downloadJSON.downloadJSON(url: "http://www.example.com/jsonfile.json")

/** waits for the first JSON object to return the json file successfully */

//re instantiate the download JSON class
downloadJSON = createDownloadJSONObject()

//Downloads and parses the file correctly
downloadJSON.downloadJSON(url: "http://www.example.com/jsonfile.json")

//happy days

【讨论】:

【参考方案4】:

您可以维护一个字典来存储从服务器获得的响应。 每个任务都会有独特的反应。所以创建一个字典,其中“keys”作为任务的“taskIdentifier”,“values”作为“data”。

例如: 在 didReceiveData 或任何其他等效方法 [从服务器获得响应的地方] 将响应存储在字典中,其中 taskIdentifier 作为键。

 NSString *taskID = [@(dataTask.taskIdentifier) stringValue];
[_task_data_dictionary setObject:data forKey:taskID];

这里的_task_data_dictionary是字典。这样就可以摆脱上面的错误了。

在此之后,您可以使用此代码使用相同的字典获取数据

 NSData *data = [_task_data_dictionary objectForKey:taskNumber];

再次使用 taskIdentifier 。

希望这会有所帮助。

【讨论】:

以上是关于NSJSONSerialization 无法解析有效的 JSON - “垃圾结束”的主要内容,如果未能解决你的问题,请参考以下文章

使用 NSDictionary 访问 NSJSONSerialization 数据

NSJSONSerialization 不解析这个 JSON

使用 NSJSONSerialization 解析 twitter 搜索 json 数据

使用 NSJSONSerialization 解析 JSON - 抛出 NSException

NSJSONSerialization 与基于流的解析器

NSJSONSerialization 不解析 NSManagedObject