当预期类型可能不同时,如何安全地处理来自 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 时将返回 NSDictionary
或 NSArray
(或可变变体,如果指定)。值被解析为常见的 Cocoa 类型,例如 NSString
和 NSNumber
但是我想知道在从 NSDictionary
或 NSArray
获取数据并将其解析为我的数据对象时需要多小心应用程序。我主要关心的是键的值 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 会指定可以根据多重性接收字典或数组,类似这样。在这种情况下,您应该检查类并采取相应措施。
您绝对应该检查nil
和NSNull
并优雅地处理它们。
解析器应处理损坏的 JSON,并向您返回相应的错误。
此外,您可以使用像 RestKit 这样的框架来为您映射到您的自定义对象。它作为标准进行大量数据类型检查,并且基本上将所有映射代码删除到一个简单的配置中。它还处理所有网络通信(通过 AFNetworking)。
【讨论】:
RestKit 看起来确实是一个明智的解决方案,尽管我同意大多数时候我会使用稳定的 API(尽管在过去我看到 API 在一夜之间完全切换数据结构而没有版本控制)。同样,我还看到 API 有时会返回一个整数作为值,有时会返回一个带引号的整数。 @ProgrammingThomas:一个例子是一个提供服装信息的 API,其中 14 号的衣服尺寸报告为一个数字,但“14-16”的衣服尺寸报告为一个字符串。无尽的乐趣。【参考方案2】:您需要确保您的代码可以安全抵御黑客的攻击。当您从服务器请求 JSON 时,您必须期望数据不是来自您的服务器,而是来自其他地方,并且其他人可能已将返回的数据设计为造成最大的损害。现在,如果您收到字符串而不是数字,则崩溃是非常安全的。
您必须期望您对服务器的请求会由一些大脑受损的硬件来满足,这些硬件试图“提供帮助”,例如当互联网连接失败时。您可能会收到一个“有用的”网站,而不是 JSON,该网站应该告诉用户如何重置他们的路由器。尝试使用某人的免费 WiFi 的用户可能有连接返回奇怪的结果。这通常对 JSON 没有问题,因为解析会失败(所以解析失败是您应该期望和处理的),如果您期望 html.
您必须期望您使用的公共 API 存在错误或意外行为,并且在发生这种情况时您应该表现良好。添加调试代码,这些代码至少会在您开发时记录任何意外情况。编写代码,使其适用于 API 显示的任何行为。
如果您使用自己的 API,您还应该记录任何意外情况,然后告诉服务器人员他们是否做了不该做的事情。
【讨论】:
以上是关于当预期类型可能不同时,如何安全地处理来自 JSON 的数据?的主要内容,如果未能解决你的问题,请参考以下文章
如何在主线程上安全地使用[NSTask waitUntilExit]?