当预期类型可能不同时,如何安全地处理来自 JSON 的数据?

Posted

技术标签:

【中文标题】当预期类型可能不同时,如何安全地处理来自 JSON 的数据?【英文标题】:How to safely treat data from JSON when the expected type may differ? 【发布时间】:2013-09-22 18:02:22 【问题描述】:

ios 5 和 OSX 10.7 及更高版本开始,使用 NSJSONSerialization 解析 JSON 非常容易,解析 JSON 时将返回 NSDictionaryNSArray(或可变变体,如果指定)。值被解析为常见的 Cocoa 类型,例如 NSStringNSNumber 但是我想知道在从 NSDictionaryNSArray 获取数据并将其解析为我的数据对象时需要多小心应用程序。我主要关心的是键的值 a) 是否不是 nil 并且 b) 是否不是意外类型。

例如,假设我有以下 JSON 对象:


  "version":1,
  "title":"Some interesting title",
  "info":"Some detail here"

目前,这将被解析为 NSDictionary:

@
  @"version": @1,
  @"title":@"Some interesting title",
  @"info": @"Some detail here"

我的问题是在检查返回的数据类型时我应该多小心。理论上,如果我使用了一个好的 API,我应该总是得到版本键的数值,但是如果由于某种原因它被更改为服务器端如下:

 "version:"1", ... 

甚至更糟:

 "version:"one", ...  

如果我尝试以下代码,我会遇到异常并且我的应用程序会崩溃:

NSNumber * myNumber = dictionary[@"version"];
if ([myNumber isEqualToNumber:@1])

   ...

代码不会执行,因为 a) dictionary[@"version"] 将是一个 NSString 并且 b) isEqualToNumber: 仅在 NSNumber 上可用(无法识别的选择器异常,应用程序会崩溃)。

同样,如果“信息”的 JSON 更改为以下内容,也会出现问题:


   "info":
     "code":200,
     "message":"Some detail here"
   

如果我的应用程序期望 NSString 键为 info 它将再次崩溃,因为将找到一个 NSDictionary。

在很大程度上,来自 API 或文件的大多数 JSON 都应该是健全的,并且受当前版本的应用程序支持,人们希望所有 JSON 都经过版本控制并在服务器端正确编码。在某些情况下,如果 JSON 已损坏或修改,应用程序可能会崩溃,我想避免这种情况。

可能的解决方案:

检查isKindOfClass:respondsToSelector: 的每个键/值对,只有在为真时才继续 检查密钥是否存在,如果不存在则产生错误 在 try/catch 块中包装所有内容,但是我宁愿使用可以使用的内容,如果数据有问题,则会产生错误。这可能会导致很多 @try/@catch 语句相互包含

这些解决方案中的每一个都相当庞大,并且在我的代码中添加了很多我希望尽可能避免的代码(并且在使用“好”JSON 时,这是完全可能的)。如果有替代解决方案可以处理解析 JSON 的过程,在将其放入自定义对象之前检查键的类型和值,我很想知道。

【问题讨论】:

在 C#/.NET 中,我总是反序列化为“POCO”(根据我的需要,我可以映射值、丢弃值或分解)。有没有这样一种方法可以在 Obj-C 中自动映射(并验证数据)? @user2246674 不幸的是,在 Obj-C 中没有本地方法可以做到这一点。 无赖 :( 我四处寻找,发现github.com/RestKit/RestKit - 尽管这样的事情可能有点过头了。 @user2246674 在我的情况下,这可能有点矫枉过正,在我当前的项目中,我正在使用本地 JSON 文件而不是来自网络的文件(尽管我认为 RestKit 与它们一样好用)。 一般的解决方案是在需要的地方使用isKindOfClass。但是,在任何合理的情况下,可能的变化数量都应该很小并且定义得很好。 【参考方案1】:

您通常应该针对稳定的 API 运行。您担心的更改应该伴随着任何合理系统中的版本号更改,这将使您的应用程序免受更改直到适当的升级时间。因此,您通常应该知道预期的数据类型。

在某些情况下,API 会指定可以根据多重性接收字典或数组,类似这样。在这种情况下,您应该检查类并采取相应措施。

您绝对应该检查nilNSNull 并优雅地处理它们。

解析器应处理损坏的 JSON,并向您返回相应的错误。

此外,您可以使用像 RestKit 这样的框架来为您映射到您的自定义对象。它作为标准进行大量数据类型检查,并且基本上将所有映射代码删除到一个简单的配置中。它还处理所有网络通信(通过 AFNetworking)。

【讨论】:

RestKit 看起来确实是一个明智的解决方案,尽管我同意大多数时候我会使用稳定的 API(尽管在过去我看到 API 在一夜之间完全切换数据结构而没有版本控制)。同样,我还看到 API 有时会返回一个整数作为值,有时会返回一个带引号的整数。 @ProgrammingThomas:一个例子是一个提供服装信息的 API,其中 14 号的衣服尺寸报告为一个数字,但“14-16”的衣服尺寸报告为一个字符串。无尽的乐趣。【参考方案2】:

您需要确保您的代码可以安全抵御黑客的攻击。当您从服务器请求 JSON 时,您必须期望数据不是来自您的服务器,而是来自其他地方,并且其他人可能已将返回的数据设计为造成最大的损害。现在,如果您收到字符串而不是数字,则崩溃是非常安全的。

您必须期望您对服务器的请求会由一些大脑受损的硬件来满足,这些硬件试图“提供帮助”,例如当互联网连接失败时。您可能会收到一个“有用的”网站,而不是 JSON,该网站应该告诉用户如何重置他们的路由器。尝试使用某人的免费 WiFi 的用户可能有连接返回奇怪的结果。这通常对 JSON 没有问题,因为解析会失败(所以解析失败是您应该期望和处理的),如果您期望 html.

您必须期望您使用的公共 API 存在错误或意外行为,并且在发生这种情况时您应该表现良好。添加调试代码,这些代码至少会在您开发时记录任何意外情况。编写代码,使其适用于 API 显示的任何行为。

如果您使用自己的 API,您还应该记录任何意外情况,然后告诉服务器人员他们是否做了不该做的事情。

【讨论】:

以上是关于当预期类型可能不同时,如何安全地处理来自 JSON 的数据?的主要内容,如果未能解决你的问题,请参考以下文章

我们如何在保持类型安全的同时通用地处理关联类型

线程安全与线程不安全

Nil 与预期的参数类型“JSON”不兼容

如何在主线程上安全地使用[NSTask waitUntilExit]?

如何在 UITableView 中优雅地处理来自网络的可变长度文本?

当列表的各个项目使用Retrofit和Gson的格式不同时,如何解析json列表?