swift在4.0之前,一直没有一套数据解析的方法。现在4.0后,终于有了Codable可以直接将json转成对象,有望取代OC的KVC机制。
先来看看Codable
public typealias Codable = Decodable & Encodable
它和NSCoding很像,可以重写decoder和encoder。基本的使用很简单
class Person: Codable {
var name : String = ""
var age : Int = 0
}
//json字符串
let JSONString = "{"name":"xiaoming","age":10}"
guard let jsonData = JSONString.data(using: .utf8) else {
return
}
let decoder = JSONDecoder()
guard let obj = try? decoder.decode(Person.self, from: jsonData) else {
return
}
print(obj.name) //xiaoming
print(obj.age) //10
说到Codable就离不开CodingKey ,上面的例子只能适用于json数据与model属性一一对应的情况下使用,且对应的key也要一致才可以,而CodingKey可以解决开发中经常遇到的后台给与的key与app不一样的情况
class Person: Codable {
var name : String = ""
var age : Int = 0
enum CodingKeys : String, CodingKey {
case name = "name_a"
case age
}
}
//json字符串
let JSONString = "{"name_a":"xiaoming","age":10}"
guard let jsonData = JSONString.data(using: .utf8) else {
return
}
let decoder = JSONDecoder()
guard let obj = try? decoder.decode(Person.self, from: jsonData) else {
return
}
print(obj.name) //xiaoming
同时,如果遇到部分数据不需要接收的情况,CodingKey也可以处理,主要在CodingKeys的枚举中不要写这个key就可以了
class Person: Codable {
var name : String = ""
var age : Int = 0
var score : Double = 0.00
enum CodingKeys : String, CodingKey {
case name = "name_a"
case age
}
}
//json字符串
let JSONString = "{"name_a":"xiaoming","age":10,"score":98.5}"
guard let jsonData = JSONString.data(using: .utf8) else {
return
}
let decoder = JSONDecoder()
guard let obj = try? decoder.decode(Person.self, from: jsonData) else {
return
}
print(obj.score) //0.00
基于Codable的机制,可以简单封装一下,用于项目中。现在常见的网络框架,会直接把json数据转成字典,因此增加了字典转json和json转字典的方法。
//
// Custom.swift
// 映射反射
//
// Created by qiuchengxiang@gmail.com on 2018/1/3.
// Copyright © 2018年 Zillion Fortune. All rights reserved.
//
import Foundation
fileprivate enum MapError: Error {
case jsonToModelFail //json转model失败
case jsonToDataFail //json转data失败
case dictToJsonFail //字典转json失败
case jsonToArrFail //json转数组失败
case modelToJsonFail //model转json失败
}
protocol Mappable: Codable {
func modelMapFinished()
mutating func structMapFinished()
}
extension Mappable {
func modelMapFinished() {}
mutating func structMapFinished() {}
//模型转字典
func reflectToDict() -> [String:Any] {
let mirro = Mirror(reflecting: self)
var dict = [String:Any]()
for case let (key?, value) in mirro.children {
dict[key] = value
}
return dict
}
//字典转模型
static func mapFromDict(_ dict : [String:Any], _ type:T.Type) throws -> T {
guard let JSONString = dict.toJSONString() else {
print(MapError.dictToJsonFail)
throw MapError.dictToJsonFail
}
guard let jsonData = JSONString.data(using: .utf8) else {
print(MapError.jsonToDataFail)
throw MapError.jsonToDataFail
}
let decoder = JSONDecoder()
if let obj = try? decoder.decode(type, from: jsonData) {
var vobj = obj
let mirro = Mirror(reflecting: vobj)
if mirro.displayStyle == Mirror.DisplayStyle.struct {
vobj.structMapFinished()
}
if mirro.displayStyle == Mirror.DisplayStyle.class {
vobj.modelMapFinished()
}
return vobj
}
print(MapError.jsonToModelFail)
throw MapError.jsonToModelFail
}
//JSON转模型
static func mapFromJson(_ JSONString : String, _ type:T.Type) throws -> T {
guard let jsonData = JSONString.data(using: .utf8) else {
print(MapError.jsonToDataFail)
throw MapError.jsonToDataFail
}
let decoder = JSONDecoder()
if let obj = try? decoder.decode(type, from: jsonData) {
return obj
}
print(MapError.jsonToModelFail)
throw MapError.jsonToModelFail
}
//模型转json字符串
func toJSONString() throws -> String {
if let str = self.reflectToDict().toJSONString() {
return str
}
print(MapError.modelToJsonFail)
throw MapError.modelToJsonFail
}
}
extension Array {
func toJSONString() -> String? {
if (!JSONSerialization.isValidJSONObject(self)) {
print("dict转json失败")
return nil
}
if let newData : Data = try? JSONSerialization.data(withJSONObject: self, options: []) {
let JSONString = NSString(data:newData as Data,encoding: String.Encoding.utf8.rawValue)
return JSONString as String? ?? nil
}
print("dict转json失败")
return nil
}
func mapFromJson(_ type:[T].Type) throws -> Array {
guard let JSONString = self.toJSONString() else {
print(MapError.dictToJsonFail)
throw MapError.dictToJsonFail
}
guard let jsonData = JSONString.data(using: .utf8) else {
print(MapError.jsonToDataFail)
throw MapError.jsonToDataFail
}
let decoder = JSONDecoder()
if let obj = try? decoder.decode(type, from: jsonData) {
return obj
}
print(MapError.jsonToArrFail)
throw MapError.jsonToArrFail
}
}
extension Dictionary {
func toJSONString() -> String? {
if (!JSONSerialization.isValidJSONObject(self)) {
print("dict转json失败")
return nil
}
if let newData : Data = try? JSONSerialization.data(withJSONObject: self, options: []) {
let JSONString = NSString(data:newData as Data,encoding: String.Encoding.utf8.rawValue)
return JSONString as String? ?? nil
}
print("dict转json失败")
return nil
}
}
extension String {
func toDict() -> [String:Any]? {
guard let jsonData:Data = self.data(using: .utf8) else {
print("json转dict失败")
return nil
}
if let dict = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) {
return dict as? [String : Any] ?? ["":""]
}
print("json转dict失败")
return nil
}
}
enum BeerStyle : String, Codable {
case ipa
case stout
case kolsch
// ...
}
struct Beers: Mappable {
var arrs : [Beer]
var name : String
mutating func structMapFinished() {
name = "ngdgdfg"
}
struct Beer: Mappable {
var name: String = "fsdf"
var brewery: String = "fsdfsdf"
var style: BeerStyle = .ipa
var score: Double = 0.00
var p: Person?
enum CodingKeys : String, CodingKey {
case name = "name_a"
case brewery
case p
case score
}
class Person: Mappable {
var name : String = ""
var age : Int = 0
}
}
}
let dict1 = ["name_a":"nckjs","brewery":"gdfge","style":"stout","score":60.3,"p":["name":"jokh","age":10]] as [String : Any]
let dict2 = ["name_a":"nckjs","brewery":"gdfge","style":"stout","score":60.3,"p":["name":"jokh","age":10]] as [String : Any]
let dict3 = ["name_a":"nckjs","brewery":"gdfge","style":"stout","score":60.3,"p":["name":"jokh","age":10]] as [String : Any]
let arr = [dict1,dict2,dict3]
let dict4 = ["arrs" : arr, "name":"fgsdfs"] as [String : Any]
if let beers = try? Beers.mapFromDict(dict4, Beers.self) {
print(beers)
}
这里增加了map完成后的回调方法,因为Codable的解析,并不是重新对象属性的set方法,和OC的KVC原理不一样。所以,在做一些赋值完成后的操作,需要使用这个回调方法。同时,需要注意区分class和struct的类型,选择对应的方法
因为有人留言,问到后台可能每个值都会返回null的情况,又不想把属性设为可选的,所以增加了对null的处理,如果遇到null,就需要重新init(from decoder: Decoder) throws的方法
class Person: Codable {
var name : String = ""
var age : Int = 0
init() {
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
age = try container.decodeNil(forKey: .age) ? 10 : try container.decode(Int.self, forKey: .age)
name = try container.decode(String.self, forKey: .name)
}
}
let p = Person()
let str = "{\"name\":\"fdfgdf\",\"age\":null}"
let jsonData = str.data(using: .utf8)
let decoder = JSONDecoder()
if let obj = try? decoder.decode(Person.self, from: jsonData!) {
print(obj.name,obj.age)
}else {
print("解析失败")
}
decodeNil(forKey key: KeyedDecodingContainer.Key) throws -> Bool
这个方法就是判断在解析时,对应的属性是否是null值,如果是会返回true,如果不是null就会返回false
以上封装基本可以在一些中小型项目中使用,因为swift是一门强类型语言,使用时要注意json数据的类型要和model属性的类型一一对应,否则会解析失败。
参考:http://www.cocoachina.com/swift/20170630/19691.html
demo:https://github.com/NickQCX/CXMap/blob/master/README.md