美文网首页
使用Codable解析JSON

使用Codable解析JSON

作者: 醉看红尘这场梦 | 来源:发表于2020-03-23 11:38 被阅读0次

对于如何处理JSON,尽管之前也有很多优秀的第三方代码,但在Swift 4,Apple终于给出了官方答案。作为开始,我们先来看一些简单情况的处理。

感受下Swift 4的原生处理方式

假设,我们有一段表示泊学视频信息的JSON:

let response = """
{
    "title": "How to parse JSON in Swift 4",
    "series": "What's new in Swift 4",
    "creator": "Mars",
    "type": "free"
}
"""

为了把这段JSON存到本地的Model里,我们需要定义两个类型:

enum EpisodeType: String {
    case free
    case paid
}

struct Episode {
    var title: String
    var series: String
    var creator: String
    var type: EpisodeType
}

接下来,为了可以把之前的response自动转换成Episode对象,需要满足两个条件:

  • Episode中的每个属性名,和JSON中的要一致,例如,它们都是titleseriescreatortype
  • 参与类型转换的所有的类型,都遵从Codable,它的定义是这样的:typealias Codable = Decodable & Encodable。其中Decodable表示把JSON的字符串表示映射到一个Swift model,而Encodable则表示把Swift model映射成JSON的字符串表示;

于是,我们只要把EpisodeEpisodeType的类型都改成这样:

enum EpisodeType: String, Codable { // ... }
struct Episode: Codable { // ... }

之前的response就可以自动映射到Episode对象了,无需我们再进行任何类型转换操作:

// 1\. Create a data object
var data = response.data(using: .utf8)!
let decoder = JSONDecoder()

// 2\. Decode the data
let episode = try! decoder.decode(
    Episode.self, from: data)

// 3\. Get the result
print(episode)

在上面这段代码里,我先们创建了一个JSONDecoder对象,然后,最关键的部分,是我们直接调用decoder.decode方法完成字符串到类型的映射。只要把Model的类型作为第一个参数传递给它就好了,我们无须再根据每一个JSON中的Key,手动把AnyObject转换成对应的类型。

我们来看下decode的声明:

func decode<T>(_ type: T.Type,
    from data: Data) throws -> T where T : Decodable

可以看到,它返回的,就是第一个参数类型对应的对象。于是,在上面的例子里,就可以通过type inference得到变量episode的类型了。当然,由于这个转换是有可能失败的,因此,decode可能抛出异常,这里,简单起见,我们直接使用了try!,稍后,我们会专门提到这个异常的处理方法。

如何自定义JSON中的Key

怎么样,是不是很简单?至此一切工作顺利。但现实情况可不像例子中这么简单,有时候服务器返回的JSON中Key的命名方式,和Swift代码变量的命名方式并不一致,例如,在Swift里,我们习惯使用驼峰式的命名:

struct Episode: Codable {
    ...
    let createdBy: String
    ...
}

但服务器接受的JSON中,key通常是用下划线分割的:

let response = """
{
    ...
    "created_by": "Mars",
    ...
}
"""

为了解决这个问题,我们来想一下:为什么JSON key和Model属性名称一样的时候,就可以自动完成映射呢?实际上,为了实现这个过程,编译器会在遵从了Codable的类型中安插一个enum CodingKeys: String, CodingKey类型,并通过这个类型完成映射。

因此,为了自定义映射规则,我们只要重定义CodingKeys这个类型就好了:

struct Episode: Codable {
    // ...

    enum CodingKeys: String, CodingKey {
        case title
        case series
        case type
        case createdBy = "created_by"
    }
}

这样,就通过associated value的方式,完成了JSON key到model属性的映射。我们可以用下面的代码来试一下:

let episode = Episode(
    title: "How to parse JSON in Swift 4",
    series: "What's new in Swift 4",
    createdBy: "Mars",
    type: .free)

let encoder = JSONEncoder()
data = try! encoder.encode(episode)
print(String(data: data, encoding: .utf8)!)

执行一下就可以看到,当我们把episode编码成JSON字符串的时候,就会得到下面这样的结果了:

{"series":"What's new in Swift 4","title":"How to parse JSON in Swift 4","created_by":"Mars","type":"free"}

当然,为了让这个结果打印出来好看一些,我们还可以设置JSONEncoder的一些属性:

encoder.outputFormatting = .prettyPrinted
data = try! json.encode(episode)

这样,打印出来的结果就会变成这样:

{
  "series" : "What's new in Swift 4",
  "title" : "How to parse JSON in Swift 4",
  "created_by" : "Mars",
  "type" : "free"
}

处理JSON中的日期

至此,我们的JSON都还比较简单,因为它包含的值对应到model中,都是字符串类型。但实际情况并不如此,在JSON里还会用字符串的形式包含其他类型的值,例如时间。我们给Episode新增一个属性:

struct Episode: Codable {
    // ...
    let createdAt: Date

    enum CodingKeys: String, CodingKey {
        // ...
        case createdAt = "created_at"
    }
}

当我们重新编码episode对象的时候:

let episode = Episode(
    title: "How to parse JSON in Swift 4",
    series: "What's new in Swift 4",
    createdBy: "Mars",
    type: .free,
    createdAt: Date())

data = try! encoder.encode(episode)
print(String(data: data, encoding: .utf8)!)

就会看到下面这样的结果:

{
  "series" : "What's new in Swift 4",
  "title" : "How to parse JSON in Swift 4",
  "created_by" : "Mars",
  "type" : "free",
  "created_at" : 525144946.403841
}

可以看到,编码过的日期是个奇怪的浮点数,为了让它变成我们习惯阅读的格式,我们同样需要设定JSONEncoder的选项:

encoder.dateEncodingStrategy = .iso8601

这样,编码出来的结果就好看多了:

{
  "series" : "What's new in Swift 4",
  "title" : "How to parse JSON in Swift 4",
  "created_by" : "Mars",
  "type" : "free",
  "created_at" : "2017-08-23T01:42:42Z"
}

但是,在编码出来的结果中还包含了字符T和Z,如果我们要去掉它,可以还可以把dateEncodingStrategy设置成.custom,但是这需要我们额外掌握不少内容,因此等后面我们讲到Container概念的时候,再来回顾这个用法。

处理JSON中的浮点数

JSON中另外一类非字符串的内容,是浮点数。例如,我们给Episode再添加一个表示时长的属性duration

struct Episode: Codable {
    /// ...
    let duration: Float

    enum CodingKeys: String, CodingKey {
        /// ...
        case duration
    }
}

然后,当我们解码下面这段JSON的时候:

{
    "title": "How to parse JSON in Swift 4",
    "series": "What's new in Swift 4",
    "created_by": "Mars",
    "type": "free",
    "created_at": "2017-08-23T01:42:42Z",
    "duration": "6.5"
}

就会自动把它转换成一个Episode对象了:

data = response.data(using: .utf8)!
decoder.dateDecodingStrategy = .iso8601
let episode = decoder.decode(Episode.self, from: data)

但当我们要处理一些“特殊值”的时候,就不这么简单了,例如NaN+Infinity-Infinity等。当JSON字符串中存在着这类数值的时候,它们就无法自动转换成Swift中的FloatDouble了。为此,我们也需要定义相关的转换规则:

decoder.nonConformingFloatDecodingStrategy =
    .convertFromString(
        positiveInfinity: "+Infinity",
        negativeInfinity: "-Infinity",
        nan: "NaN")

然后,当由于某种原因服务器返回的durationNaN的时候,:

{
    "title": "How to parse JSON in Swift 4",
    "series": "What's new in Swift 4",
    "created_by": "Mars",
    "type": "free",
    "created_at": "2017-08-23T01:42:42Z",
    "duration": "NaN"
}

当我们查看episode.duration的时候,就会看到它的值是nan了:

dump(episode.duration)
// - nan

处理JSON中的base64编码

接下来要处理的一类数据是model中的二进制数据,它们通常是服务器返回的一段base 64编码。例如:

{
    ...
    "origin": "Ym94dWVpby5jb20="
}

其中,origin是一段base 64编码过的值,它的原始值是boxueio.com。现在,为了在model中保存这个字段,我们给Episode添加一个属性:

struct Episode: Codable {
    /// ...
    var origin: Data

    enum CodingKeys: String, CodingKey {
        /// ...
        case origin
    }
}

接下来,为了能把JSON中的origin直接解码并保存到Episode.origin,我们同样需要配置下JSONDecoder

/// ...
decoder.dataDecodingStrategy = .base64
let v = try! decoder.decode(Episode.self, from: data)

dump(String(data: v.origin, encoding: .utf8)!)

这样,当我们查看dump结果的时候,就可以直接得到boxueio.com了。

处理URL

最后一个要介绍的,是JSON中的URL,JSONDecoder可以直接把它转成Swift中的URL对象。例如,服务器返回的JSON中,包含一个URL值:

{
    ...
    "url": "boxueio.com"
}

然后,我们继续给Episode添加对应的属性:

struct Episode: Codable {
    /// ...
    let url: URL

    enum CodingKeys: String, CodingKey {
        /// ...
        case url
    }
}

现在,JSONDecode就可以处理结果中的url key了。

相关文章

网友评论

      本文标题:使用Codable解析JSON

      本文链接:https://www.haomeiwen.com/subject/wxwuehtx.html