Swift Json解析与model互转

Json的解码与编码操作,这里使用swift自带的类JSONDecoderJSONEncoder

1、基础处理

如果你的 JSON 数据结构和你使用的 Model 对象结构一致的话,那么解析过程将会非常简单

2、自定义键值名

默认情形下 Keys 是由编译器自动生成的枚举类型。该枚举遵守 CodingKey 协议并建立了属性和编码后格式之间的关系

struct Beer : Codable {
// ...
private enum CodingKeys : String, CodingKey {
      case name
      case abv = "alcohol_by_volume"
      case brewery = "brewery_name"
      case style
}
}

3、时间格式处理

4、浮点类型处理

5、Data 处理

有时候服务端 API 返回的数据是 base64 编码过的字符串。

对此,我们可以在 JSONEncoder 使用以下策略:

  • .base64
  • .custom( (Data, Encoder) throws -> Void)

反之,编码时可以使用:

  • .base64
  • .custom( (Decoder) throws -> Data)

显然,.base64 时最常见的选项,但如果需要自定义的话可以采用 block 方式。

 

6、Wrapper Keys

通常 API 会对数据进行封装,这样顶级的 JSON 实体 始终是一个对象。

例如:

{
  "beers": [ {...} ]
}

在 Swift 中我们可以进行对应处理:

struct BeerList : Codable {
    let beers: [Beer]
}

因为键值与属性名一致,所有上面代码已经足够了。

7、Root Level Arrays

如果 API 作为根元素返回数组,对应解析如下所示:

let decoder = JSONDecoder()
let beers = try decoder.decode([Beer].self, from: data)

需要注意的是,我们在这里使用Array作为类型。只要 T 可解码,Array <T> 就可解码。

8、Dealing with Object Wrapping Keys

另一个常见的场景是,返回的数组对象里的每一个元素都被包装为字典类型对象。

[
  {
    "beer" : {
      "id": "uuid12459078214",
      "name": "Endeavor",
      "abv": 8.9,
      "brewery": "Saint Arnold",
      "style": "ipa"
    }
  }
]

你可以使用上面的方法来捕获此 Key 值,但最简单的方式就是认识到该结构的可编码的实现形式。

如下:

[[String:Beer]]

或者更易于阅读的形式:

Array<Dictionary<String, Beer>>

与上面的 Array<T> 类似,如果 K 和 T 是可解码 Dictionary<K,T> 就能解码。

let decoder = JSONDecoder()
let beers = try decoder.decode([[String:Beer]].self, from: data)
dump(beers)
 1 element
  ▿ 1 key/value pair
    ▿ (2 elements)
      - key: "beer"
      ▿ value: __lldb_expr_37.Beer
        - name: "Endeavor"
        - brewery: "Saint Arnold"
        - abv: 8.89999962
        - style: __lldb_expr_37.BeerStyle.ipa

9、更复杂的嵌套

有时候 API 的响应数据并不是那么简单。顶层元素不一定只是一个对象,而且通常情况下是多个字典结构。

例如:

{
    "meta": {
        "page": 1,
        "total_pages": 4,
        "per_page": 10,
        "total_records": 38
    },
    "breweries": [
        {
            "id": 1234,
            "name": "Saint Arnold"
        },
        {
            "id": 52892,
            "name": "Buffalo Bayou"
        }
    ]
}

在 Swift 中我们可以进行对应的嵌套定义处理:

struct PagedBreweries : Codable {
    struct Meta : Codable {
        let page: Int
        let totalPages: Int
        let perPage: Int
        let totalRecords: Int
        enum CodingKeys : String, CodingKey {
            case page
            case totalPages = "total_pages"
            case perPage = "per_page"
            case totalRecords = "total_records"
        }
    }

    struct Brewery : Codable {
        let id: Int
        let name: String
    }

    let meta: Meta
    let breweries: [Brewery]
}

该方法的最大优点就是对同一类型的对象做出不同的响应(可能在这种情况下,“brewery” 列表响应中只需要 id 和 name 属性,但是如果查看详细内容的话则需要更多属性内容)。因为该情形下 Brewery 类型是嵌套的,我们依旧可以在其他地方进行不同的 Brewery 类型实现。

10、无法覆盖继承的问题,自定义解码和编码

============================================代码示例============================================

model代码如下

//为了将 JSON 字符串转化为 Beer 类型的实例,我们需要将 Beer 类型标记为 Codable。

enum BeerStyle: String, Codable {
    case ipa
    case stout
    case kolsch
}

struct Beer: Codable {
    let name: String
    let abv: Float
    let brewery: String
    let style: BeerStyle
}

/// json 的key与结构体属性不是对应的,需要自定义键值名
struct Beer2: Codable {
    let name: String
    let abv: Float
    let brewery: String
    let style: BeerStyle

    private enum CodingKeys: String, CodingKey {
        case name
        case abv = "alcohol_by_volume"
        case brewery = "brewery_name"
        case style
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: Beer2.CodingKeys.name)
        abv = try values.decode(Float.self, forKey: Beer2.CodingKeys.abv)
        brewery = try values.decode(String.self, forKey: Beer2.CodingKeys.brewery)
        style = try values.decode(BeerStyle.self, forKey: Beer2.CodingKeys.style)
    }
}

class Wine: NSObject, Codable {
    var abv: Float?
}

class Beer3: Wine {
    var name: String?
    var brewery: String?
    var style: BeerStyle?
}

class Beer4: Wine {
    var name: String?
    var brewery: String?
    var style: BeerStyle?

    private enum CodingKeys: String, CodingKey {
        case name
        case abv = "alcohol_by_volume"
        case brewery = "brewery_name"
        case style
    }

    /// 自定义编码
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(name, forKey: .name)
        try container.encode(abv, forKey: .abv)
        try container.encode(brewery, forKey: .brewery)
        try container.encode(style, forKey: .style)
    }
    
    /// 自定义解码
    required init(from decoder: Decoder) throws {
        super.init()
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: Beer4.CodingKeys.name)
        abv = try values.decode(Float.self, forKey: Beer4.CodingKeys.abv)
        brewery = try values.decode(String.self, forKey: Beer4.CodingKeys.brewery)
        style = try values.decode(BeerStyle.self, forKey: Beer4.CodingKeys.style)
    }
}

/// 时间格式处理
struct Foo: Encodable {
    let date: Date
}

/// 浮点类型处理
struct Numbers: Decodable {
    let a: Float
    let b: Float
    let c: Float
}

编码与解码

import UIKit

class jsonViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        jsonFunc1()
        jsonFunc2()
        jsonFunc3()
        jsonFunc4()
        jsonFunc7()
        jsonFunc8()
    }
}

// MARK: https://segmentfault.com/a/1190000009929819#articleHeader9

extension jsonViewController {
    /// JSON 数据结构和Model 对象结构一致
    func jsonFunc1() {
        let jsonString: String = "{\"name\":\"Endeavor\",\"abv\":8.9,\"brewery\":\"Saint Arnold\",\"style\":\"ipa\"}"
        let jsonData = jsonString.data(using: String.Encoding.utf8)
        let decoder = JSONDecoder()
        // JSON 数据解析为 Beer 实例对象
        let beer = try! decoder.decode(Beer.self, from: jsonData!)
        
        print("name=\(beer.name),abv=\(beer.abv),brewery=\(beer.brewery),style=\(beer.style)")
        /* 打印
         name=Endeavor,abv=8.9,brewery=Saint Arnold,style=ipa
         */
        
        let encoder = JSONEncoder()
        // 默认 outputFormatting 属性值为 .compact,输出效果如上。如果将其改为 .prettyPrinted 后就能获得更好的阅读体检
        encoder.outputFormatting = .prettyPrinted
        // 将 Beer 实例转化为 JSON
        let jsonData2 = try! encoder.encode(beer)
        
        print(String(bytes: jsonData2, encoding: String.Encoding.utf8) ?? "")
        
        /* 打印
         {
         "style" : "ipa",
         "brewery" : "Saint Arnold",
         "name" : "Endeavor",
         "abv" : 8.8999996185302734
         }
         */
    }
    
    /// JSON 数据结构和Model 对象结构不一致,自定义键值名
    func jsonFunc2() {
        let jsonString: String = "{\"name\":\"Endeavor\",\"alcohol_by_volume\":8.9,\"brewery_name\":\"Saint Arnold\",\"style\":\"ipa\"}"
        let jsonData = jsonString.data(using: String.Encoding.utf8)
        let decoder = JSONDecoder()
        // JSON 数据解析为 Beer 实例对象
        let beer = try! decoder.decode(Beer2.self, from: jsonData!)
        
        print("name=\(beer.name),abv=\(beer.abv),brewery=\(beer.brewery),style=\(beer.style)")
        /* 打印
         name=Endeavor,abv=8.9,brewery=Saint Arnold,style=ipa
         */
        
        let encoder = JSONEncoder()
        // 默认 outputFormatting 属性值为 .compact,输出效果如上。如果将其改为 .prettyPrinted 后就能获得更好的阅读体检
        encoder.outputFormatting = .prettyPrinted
        // 将 Beer 实例转化为 JSON
        let jsonData2 = try! encoder.encode(beer)
        
        print(String(bytes: jsonData2, encoding: String.Encoding.utf8) ?? "")
        /* 打印
         {
         "style" : "ipa",
         "name" : "Endeavor",
         "brewery_name" : "Saint Arnold",
         "alcohol_by_volume" : 8.8999996185302734
         }
         */
    }
    
    /// Codable 默认实现无法覆盖继承这种情况
    func jsonFunc3() {
        let jsonDic: [String: Any] = ["name": "Endeavor", "abv": 8.9, "brewery": "Saint Arnold", "style": "ipa"]
        let jsonData = JSONToData(obj: jsonDic)
        
        let decoder = JSONDecoder()
        // JSON 数据解析为 Beer 实例对象
        let beer = try! decoder.decode(Beer3.self, from: jsonData)
        
        print("name=\(String(describing: beer.name)),abv=\(String(describing: beer.abv)),brewery=\(String(describing: beer.brewery)),style=\(String(describing: beer.style))")
        /* 打印
         name=nil,abv=Optional(8.9),brewery=nil,style=nil
         */
        
        //解析成功但是 name、brewery、style 三个属性全部为 nil,显然,这不是我们想要的结果。Codable 默认实现无法覆盖继承这种情况
    }
    
    /// 解决 无法覆盖继承的问题,自定义解码和编码
    func jsonFunc4() {
        let jsonString: String = "{\"name\":\"Endeavor\",\"alcohol_by_volume\":8.9,\"brewery_name\":\"Saint Arnold\",\"style\":\"ipa\"}"
        let jsonData = jsonString.data(using: String.Encoding.utf8)
        let decoder = JSONDecoder()
        // JSON 数据解析为 Beer 实例对象
        let beer = try! decoder.decode(Beer4.self, from: jsonData!)

        print("name=\(String(describing: beer.name)),abv=\(String(describing: beer.abv)),brewery=\(String(describing: beer.brewery)),style=\(String(describing: beer.style))")
        /* 打印
         name=Optional("Endeavor"),abv=Optional(8.9),brewery=Optional("Saint Arnold"),style=Optional(abcProject.BeerStyle.ipa)
         */
        
        let encoder = JSONEncoder()
        // 默认 outputFormatting 属性值为 .compact,输出效果如上。如果将其改为 .prettyPrinted 后就能获得更好的阅读体检
        encoder.outputFormatting = .prettyPrinted
        // 将 Beer 实例转化为 JSON
        let jsonData2 = try! encoder.encode(beer)
        
        print(String(bytes: jsonData2, encoding: String.Encoding.utf8) ?? "")
        /* 打印
         {
         "style" : "ipa",
         "name" : "Endeavor",
         "brewery_name" : "Saint Arnold",
         "alcohol_by_volume" : 8.8999996185302734
         }
         */
    }
    
    /// 时间格式处理
    func jsonFunc7() {
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601
        let foo = Foo(date: Date())
        let jsonData2 = try! encoder.encode(foo)
        
        print(String(bytes: jsonData2, encoding: String.Encoding.utf8) ?? "")
        
        /*
         其他日期编码格式选择如下:
         
         .formatted(DateFormatter) - 当你的日期字符串是非标准格式时使用。需要提供你自己的日期格式化器实例。
         .custom( (Date, Encoder) throws -> Void ) - 当你需要真正意义上的自定义时,使用一个闭包进行实现。
         .millisecondsSince1970、 .secondsSince1970 - 这在 API 设计中不是很常见。 由于时区信息完全不在编码表示中,所以不建议使用这样的格式,这使得人们更容易做出错误的假设。
         对日期进行 Decoding 时基本上是相同的选项,但是 .custom 形式是 .custom( (Decoder) throws -> Date ),所以我们给了一个解码器并将任意类型转换为日期格式。
         
         */
    }
    
    /// 浮点类型处理
    func jsonFunc8() {
        let jsonString: String = "{\"a\": \"NaN\",    \"b\": \"+Infinity\",    \"c\": \"-Infinity\" }"
        let jsonData = jsonString.data(using: String.Encoding.utf8)
        let decoder = JSONDecoder()
        
        decoder.nonConformingFloatDecodingStrategy =
            .convertFromString(
                positiveInfinity: "+Infinity",
                negativeInfinity: "-Infinity",
                nan: "NaN")
        
        let numbers = try! decoder.decode(Numbers.self, from: jsonData!)
        dump(numbers)
        
        /* 打印
         
         ▿ abcProject.Numbers
         - a: nan
         - b: inf
         - c: -inf
         
         */
    }
}

 

 

 

 

 

 

参考链接

Swift 4 JSON 解析指南

Swift 4 JSON 解析进阶

JSONDecoder的使用

posted on 2019-07-17 18:55  二狗你变了  阅读(8345)  评论(0编辑  收藏  举报

导航