Swift 中的 JSON 解析

相比于主流的 Swift JSON 库使用自动映射来处理 JSON 转模型,苹果官方反而鼓励使用简单直接的方式。这也是我一直在用的。 阅读原文 »

Swift 中的 JSON 解析

如果 app 与 web 应用通信,那么从服务端返回的消息通常是 JSON 格式的。Foundation 框架的 JSONSerialization 类可以将 JSON 转换为 Swift 的 DictionaryArrayStringNumberBool 等数据类型。但是因为无法确定 app 接收的 JSON 结构或值,所以很难正确地反序列化对象模型。本文列举了一些 app 中使用 JSON 的方法。

从 JSON 取值

JSONSerialization 类方法 jsonObject(with:options:) 可以返回 Any 类型的值,如果数据无法解析则会抛出 error。

即使有效的 JSON 可能只包含一个简单的值,web 应用的响应结果也通常会将 object 或 array 编码,作为顶层对象。在 ifguard 语句中使用可选绑定和 as? 类型强制转换运算符可以将未知类型的值提取为常量。要从一个 JSON 对象类型获得 Dictionary 值,可以按条件强制转换为 [String: Any]。要从 JSON 数组类型获得 Array 值,可以按条件将其强制转换为 [Any](或元素类型更具体的数组,比如 [String])。使用非强制绑定下标访问器或枚举模式匹配的类型强制转换,可以用 key 获取 dictionary 值或用 index 获取 array 值。

Swift 固有语言的特点使得安全获取和使用 Foundation API(无需额外的库或框架)解码的 JSON 数据很容易。

根据 JSON 获取到的值创建模型对象

大部分 Swift app 都遵循 Model-View-Controller 设计模式,所以经常将 JSON 数据转化为模型定义中针对 app 域名的具体对象。

例如,当编写一个 app,提供当地餐馆的搜索结果时,可能会用接收 JSON 对象的初始化式,和向服务器的 /search 终端发起 HTTP 请求,之后异步返回一个 Restauran 对象数组的类型方法来实现一个 Restaurant 模型。

Consider the following Restaurant model:

看看下面的 Restaurant 模型:

一个 Restaurant 拥有一个 String 类型的 name,一个二维坐标表示的 location 和包含嵌套 Meal 枚举值的 meals Set

下例展示了在服务端响应中,一个餐馆是如何表示的。

 

编写可选 JSON 的初始化式

要将 JSON 表述转换为 Restaurant 对象,可以编写一个带有获取 JSON 数据并转化为属性的 Any 参数的初始化式。

如果 app 需要和一个或多个 web 服务器通信,并且服务器返回的不是单一、连续的模型对象表述,可以考虑实现几个初始化式,处理每个可能的表述。

在上例中,通过可选绑定和 as? 类型强制转换符,JSON 字典的每个值都被提取为常量了。对 name 属性来说,按原样赋值给提取的 name 值。对 coordinate 属性来说,赋值前将提取的 latitudelongitude 值结合为元组。对 meals 属性来说,迭代提取的字符串值以构造 Meal 枚举值的 Set

编写代有错误处理的 JSON 初始化式

之前的例子实现了一个若反序列化失败则会返回 nil 的可选初始化式。也可以定义遵守 Error 协议的类型,实现一个每当反序列化失败时都会抛出相应类型 error 的初始化式。

这里的 Restaurant 类型声明了一个嵌套的 SerializationError 类型,为丢失或不合法的属性定义了带有关联值的枚举情况。此版本的 JSON 初始化式,并没有通过返回 nil 来标志失败,而是通过抛出 error 以说明具体的失败原因。这一版本也对 input 数据进行了验证,以保证 coordinates 是有效的地理坐标对,并且 JSON 中具体 meals 的每个名字都对应 Meal 枚举情况。

编写类型方法提取结果

Web 应用终端通常会在一个 JSON 中返回复杂的资源。例如,一个 /search 终端可能返回零或多个匹配请求查询参数的餐馆,包括其它元数据的表述:

这时可以在 Restaurant 结构上创建一个类型方法,将 query 方法参数转化为相应的请求对象,并向 web 服务发送 HTTP 请求。这段代码同样可以处理响应、反序列化 JSON 数据、根据 "results" 数组抽取的每项字典创建 Restaurant 对象以及在完成处理器中将它们异步返回。

当用户在搜索条中输入文本时,view controller 可以调用这个方法,用匹配的餐馆数据填充表格视图:

Separating concerns in this way provides a consistent interface for accessing restaurant resources from view controllers, even when the implementation details about the web service change.

这样的关注点分离可以为 view controller 访问餐馆资源提供一致的接口,即使 web 服务的实现细节变化了也没关系。

关于反射的反思

为了不同系统间的通信,相同数据表述之间的转换对编写软件来说是一项乏味但必要的任务。

因为这些表述的结构可能非常相似,所以建立高层次的抽象以自动比对这些不同表述可能很有吸引力。例如,一种类型可能定义了 snake_case JSON 键和 camelCase 属性名间的映射,这样就可以根据 JSON,使用 Swift reflection API(比如 Mirror)自动初始化模型。

但是我们发现这种抽象在Swift语言特点下,日常使用起来意义不大,反而使得 debug 问题或处理边界情况更加困难了。在上述例子中,初始化式不仅从 JSON 提取、映射出值,同时可以初始化复杂的数据类型、执行特定域名的输入验证。为了实现这些任务,基于映射的方式成本更高。评估 app 可用策略时记住:少量重复的成本可能会比采用不当抽象的成本小得多。

打赏支持我翻译更多好文章,谢谢!

打赏译者

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

1 收藏 评论

关于作者:古鲁伊

立志做一名有格调的程序媛 个人主页 · 我的文章 · 34

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部