Swift Json解析与model互转
Json的解码与编码操作,这里使用swift自带的类JSONDecoder 和 JSONEncoder
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 */ } }
参考链接