美文网首页
使用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