zoukankan      html  css  js  c++  java
  • 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的使用

  • 相关阅读:
    PG
    unzip
    yum
    PG
    SQL
    Grails
    Grails
    Grails
    Chrome
    HTML
  • 原文地址:https://www.cnblogs.com/dhui69/p/11203010.html
Copyright © 2011-2022 走看看