Swift Json实例详细解析

前言

客户端开发项目中,不可避免地需要解析网络数据---将服务端下发的JSON数据解析成客户端可阅读友好的Model。Objective-C下使用最多的是JSONModel,它能在OC Runtime基础下很好地完成解析工作。那么在纯Swift代码中,这个功能是如何实现的?下面开始我们的探索~

  • 手动解析
  • 原生:Swift4.0 JSONDecoder
  • JSONDecoder 问题 及 解决方案

手动解析

假设一个User类要解析,Json如下:

{
 "userId": 1,
 "name": "Jack",
 "height": 1.7,
}

对应的创建一个User结构体(也可以是类):

struct User {
 var userId: Int?
 var name: String?
 var height: CGFloat?
}

把JSON转成User

在Swift4.0前,我们以手动解析的方式将JSON model化。给User加一个以JSON为参数的初始化方法,代码如下:

struct User {
 ...
 init?(json: [String: Any]) {
  guard let userId = json["userId"] as? Int,
  let name = json["name"] as? String,
  let height = json["height"] as? CGFloat else { return nil }
  self.userId = userId
  self.name = name
  self.height = height
 }
}

依次从json中取出model所需的具体类型的数据,填充到具体对应属性中。如果其中一个转换失败或者没有值,初始化会失败返回nil。

如果某个值不需要强校验,直接取值再赋值,把guard let内的语句去掉。例如,若height不用校验,可看如下代码:

struct User {
 ...
 init?(json: [String: Any]) {
  guard let userId = json["userId"] as? Int,
  let name = json["name"] as? String else { return nil }
  self.userId = userId
  self.name = name
  self.height = json["height"] as? CGFloat
 }
}

原生:Swift4.0 JSONDecoder

2017年6月份左右Swift4.0发布,其中一个重大更新就是JSON的加解密。摆脱手工解析字段的繁琐,聊聊几行代码就可将JSON转换成Model。与Objective-C下的JSONModel极为相似。同样解析上述例子中的User,Swift4.0可以这么写:

struct User: Decodable {
 var userId: Int?
 var name: String?
 var height: CGFloat?
}

let decoder = JSONDecoder()
if let data = jsonString.data(using: String.Encoding.utf8) {
 let user = try? decoder.decode(User.self, from: data)
}

so easy~ 与手动解析不同点在于:

1.移除了手写init?方法。不需要手动解了

2.User实现Decodable协议,协议的定义如下:

/// A type that can decode itself from an external representation.
public protocol Decodable {
 /// Creates a new instance by decoding from the given decoder.
 ///
 /// This initializer throws an error if reading from the decoder fails, or
 /// if the data read is corrupted or otherwise invalid.
 ///
 /// - Parameter decoder: The decoder to read data from.
 public init(from decoder: Decoder) throws
}

Decodable协议只有一个方法public init(from decoder: Decoder) throws---以Decoder实例进行初始化,初始化失败可能抛出异常。庆幸的是,只要继承Decodable协议,系统会自动检测类中的属性进行初始化工作,省去了人工解析的麻烦~

3.使用了JSONDecoder。它是真正的解析工具,主导整个解析过程

读到这里,是不是觉得人生从黑暗迈向了光明~~

可是,它并不完美...

JSONDecoder问题及方案

解析JSON经常遇到这样两种不一致问题:

  • 服务端下发的key跟端上不一致。比如,服务端下发key="order_id",端上定义key="orderId"
  • 服务端下发的日期表达是yyyy-MM-dd HH:mm或者时间戳,但端上是Date类型
  • 服务端下发的基本类型和端上定义的不一致。服务端下发的是String,端上定义的Int,等

前两个问题JSONDecoder都能很好地解决。

第一个key不一致问题,JSONDecoder有现成的方案。以上面介绍的例子来说,假设服务端返回的key是user_id而不是userId,那么我们可以使用JSONDecoder的CodingKeys像JSONModel一样对属性名称在加解密时的名称做转换。User修改如下:

struct User: Decodable {
 var userId: Int?
 var name: String?
 var height: CGFloat?
 enum CodingKeys: String, CodingKey {
  case userId = "user_id"
  case name
  case height
 }
}

第二个,Date转换问题。JSONDecoder也为我们提供了单独的API:

open class JSONDecoder {
 /// The strategy to use for decoding `Date` values.
 public enum DateDecodingStrategy {

  /// Defer to `Date` for decoding. This is the default strategy.
  case deferredToDate

  /// Decode the `Date` as a UNIX timestamp from a JSON number.
  case secondsSince1970

  /// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
  case millisecondsSince1970

  /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
  case iso8601

  /// Decode the `Date` as a string parsed by the given formatter.
  case formatted(DateFormatter)

  /// Decode the `Date` as a custom value decoded by the given closure.
  case custom((Decoder) throws -> Date)
 }
 ......
 /// The strategy to use in decoding dates. Defaults to `.deferredToDate`.
 open var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy
}

设置好了JSONDecoder属性dateDecodingStrategy后,解析Date类型就会按照指定的策略进行解析。

类型不一致

至此,JSONDecoder为我们提供了

  • 解析不同key值对象
  • Date类型可自定义转换
  • Float在一些正负无穷及无值得特殊表示。(出现的概率很少,不作具体说明了)

但遇到基本类型端上与服务端不一致时(比如一个数字1,端上的Code是Int型,服务端下发String:"1"),JSONDecoder会抛出typeMismatch异常而终结整个数据的解析。

这让人有点懊恼,端上的应用,我们希望它能够尽可能稳定,而不是某些情况下遇到若干个基本类型不一致整个解析就停止,甚至是 Crash。

如下面表格所示,我们希望类型不匹配时,能够这么处理:左列代表前端的类型,右列代表服务端类型,每一行代表前端类型为X时,能从服务端下发的哪些类型中转化,比如String 可以从 IntorFloat转化。这几个类型基本能覆盖日常服务端下发的数据,其它类型的转化可根据自己的需求扩充。

前端 服务端
String Int,Float
Float String
Double String
Bool String, Int

JSONDecoder没有给我们便利的这种异常处理的API。如何解决呢?最直接的想法,在具体的model内实现init(decoder: Decoder)手动解析可以实现,但每个都这么处理太麻烦。

解决方案:KeyedDecodingContainer方法覆盖

研究JSONDecoder的源码,在解析自定义Model过程中,会发现这样一个调用关系。

// 入口方法
JSONDecoder decoder(type:Type data:Data)
 // 内部类,真实用来解析的
 _JSONDecoder unbox(value:Any type:Type)
 // Model调用init方法
 Decodable init(decoder: Decoder)
 // 自动生成的init方法调用container
 Decoder container(keyedBy:CodingKeys)
 // 解析的容器
 KeyedDecodingContainer decoderIfPresent(type:Type) or decode(type:Type)
  // 内部类,循环调用unbox
  _JSONDecoder unbox(value:Any type:Type)
  ...循环,直到基本类型

最终的解析落到,_JSONDecoder的unbox 及 KeyedDecodingContainer的decoderIfPresent decode方法。但_JSONDecoder是内部类,我们处理不了。最终决定对KeyedDecodingContainer下手,其中部分代码如下:

extension KeyedDecodingContainer {
 .......
 /// Decode (Int, String) -> Int if possiable
 public func decodeIfPresent(_ type: Int.Type, forKey key: K) throws -> Int? {
  if let value = try? decode(type, forKey: key) {
   return value
  }
  if let value = try? decode(String.self, forKey: key) {
   return Int(value)
  }
  return nil
 }

 .......

 /// Avoid the failure just when decoding type of Dictionary, Array, SubModel failed
 public func decodeIfPresent<T>(_ type: T.Type, forKey key: K) throws -> T? where T : Decodable {
  return try? decode(type, forKey: key)
 }
}

上述代码中,第一个函数decodeIfPresent(_ type: Int.Type, forKey key: K)是以key的信息解析出Int?值。这里覆盖了KeyedDecodingContainer中的该函数的实现,现在已try?的形式以Int类型解析,解析成功则直接返回,失败则以String类型解析出一个StringValue,如果解析成功,再把String转换成Int?值。

为什么要写第二个函数呢?

场景:当我们Model内有其他的非基本类型的Model,比如其他自定义Model,Dictionary<String, Any>,Array<String>等,当这些Model 类型不匹配或者出错误时也会抛出异常,导致整个大Model解析失败。
覆盖decodeIfPresent<T>(_ type: T.Type, forKey key: K)可以避免这些场景。至此,当类型过程中出现解析的Optional类型出现不匹配时,我们要不是通过转换,要不就是给其赋值nil,避免了系统此时直接throw exception导致退出整个解析过程的尴尬。

为何不覆盖decode方法?decodeIfPresent可以返回Optional值,decode返回确定类型值。考虑到如果Model内如果定义的类型是No-Optional型,那么可以认为开发者确定该值必须存在,如果不存在Model很可能是错误的,所以直接fail。

完整扩展代码点我   (本地下载点我)

总结

Swift4.0 JSONDecoder确实为解析数据带来了极大的便利。使用方式上类似Objective-C下的JSONModel。但实际开发中还是需要一些改造才能更好地服务于我们。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • iOS Swift读取本地json文件报错的解决方法

    前言 最近闲来无聊,本地了一个json读取,但没想到在用Swift测试数据发现加载本地json文件一直报以下错误: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with a

  • Swift中字典与JSON转换的方法

    Swift中经常会遇到字典和字符串的相互转换,因此可以转换可以封装起来,转换代码如下: func convertStringToDictionary(text: String) -> [String:AnyObject]? { if let data = text.data(using: String.Encoding.utf8) { do { return try JSONSerialization.jsonObject(with: data, options: [JSONSerializat

  • Swift利用Decodable解析JSON的一个小问题详解

    前言 Swift 4是苹果计划于2017年秋季推出的最新版本,其主要重点是提供与Swift 3代码的源兼容性,并努力实现ABI稳定性.从Swift4开始提供的Decodable解析JSON确实很方便,但遇到一个小问题,记录一下. 当JSON中某个key的值为{}或者空字符串"",而该值需要解析的不是基本类型时,即使标记为 Optional,依然会导致整个解析失败: //: Playground import Foundation //Book.swift struct Book: Co

  • Swift Json实例详细解析

    前言 客户端开发项目中,不可避免地需要解析网络数据---将服务端下发的JSON数据解析成客户端可阅读友好的Model.Objective-C下使用最多的是JSONModel,它能在OC Runtime基础下很好地完成解析工作.那么在纯Swift代码中,这个功能是如何实现的?下面开始我们的探索~ 手动解析 原生:Swift4.0 JSONDecoder JSONDecoder 问题 及 解决方案 手动解析 假设一个User类要解析,Json如下: { "userId": 1, "

  • json 实例详细说明教程

    官方网站地址:http://www.json.org/json-zh.htmljson2.js 我们下载地址 复制代码 代码如下: <script type="text/javascript" src="json2.js"></script> <script> //直接声明json数据结构 var myJSONObject = {"bindings": [ {"ircEvent": &quo

  • javascript遍历控件实例详细解析

    js遍历页面控件, 复制代码 代码如下: var inputArr = document.forms[0];   for( var i = 0; i < inputArr.length; i++ ) {    if( inputArr[i].type.toUpperCase() == "BUTTON" ) {     inputArr[i].disabled="disabled";    }else if( inputArr[i].type.toUpperCa

  • Python 对象序列化与反序列化之pickle json详细解析

    目录 引言 pickle json 尾语 引言 将对象的状态信息转换为可以存储或传输的形式的过程叫作序列化 类似地从序列化后的数据转换成相对应的对象叫作 反序列化 本文介绍 Python 将对象序列化和反序化的两个模块 picklejson pickle pickle # 序列化 In [19]: num = 66 In [20]: s = 'python' In [21]: pi = 3.14 In [22]: li = [1, 2, 3] In [27]: b_num = pickle.du

  • 什么是json和jsonp,jQuery json实例详详细说明

    什么是JSON? 前面简单说了一下,JSON是一种基于文本的数据交换方式,或者叫做数据描述格式,你是否该选用他首先肯定要关注它所拥有的优点. JSON的优点: 1.基于纯文本,跨平台传递极其简单: 2.Javascript原生支持,后台语言几乎全部支持: 3.轻量级数据格式,占用字符数量极少,特别适合互联网传递: 4.可读性较强,虽然比不上XML那么一目了然,但在合理的依次缩进之后还是很容易识别的: 5.容易编写和解析,当然前提是你要知道数据结构: JSON的缺点当然也有,但在作者看来实在是无关

  • IOS 简单的本地json格式文件解析的实例详解

    IOS 简单的本地json格式文件解析的实例详解 ljweibo.json文件 { "data":[{ "name" : "孙悟空", "content" : "7月12日的国务院常务会议上,李克强明确要求,要将已审议的<快递条例(草案)>向社会公开征求意见.在会上,总理说了这么一段话:"几年前,快递业刚刚开始发展的时候,有些城市不允许快递存在,理由是影响市容整洁,快递员骑的摩的也不允许停放.但

  • jQuery解析Json实例详解

    本文实例讲述了jQuery解析Json的方法.分享给大家供大家参考,具体如下: 前言 在WEB数据传输过程中,json是以文本,即字符串的轻量级形式传递的,而客户端一般用JS操作的是接收到的JSON对象,所以,JSON对象和JSON字符串之间的相互转换.JSON数据的解析是关键. 先明确2个概念例如: JSON字符串: 复制代码 代码如下: var str1 = '{ "name": "deyuyi", "sex": "man"

  • ajax动态加载json数据并详细解析

    效果图 jsp代码 <form > 姓名:<input name="name" type="text"/> 年龄:<input name="age" type="text"/> <input type="button" class="get" value="get提交"/> <input type="bu

  • 读取本地json文件,解析json(实例讲解)

    模拟用户登录 # data.json 文件同目录下 [ { "id": 1, "username": "zhangshan", "password": "123qwe", "lock": false }, { "id": 2, "username": "lisi", "password": "123

  • 利用Jackson解析JSON的详细实现教程

    目录 JSON 介绍 Jackson 介绍 Jackson Maven 依赖 ObjectMapper 对象映射器 Jackson JSON 基本操作 Jackson JSON 序列化 Jackson JSON 反序列化 JSON 转 List JSON 转 Map Jackson 忽略字段 Jackson 日期格式化 Date 类型 LocalDateTime 类型 时间格式化 Jackson 常用注解 @JsonIgnore @JsonGetter @JsonSetter @JsonAnyS

随机推荐