前语
Codable 是随 Swift 4.0 推出的,旨在取代现有的 NSCoding 协议,支持结构体、枚举和类,能够将 JSON 这种弱数据类型转化成代码中运用的强数据类型。
Codable 是一个协议,同时统筹了 Decodable 和 Encodable 两个协议,假如你界说了一个数据类型遵照了 Codable 协议,其实是遵照了 Decodable 和 Encodable:
typealias Codable = Decodable & Encodable
说白了便是一套转化协议,能够让数据和 Swift 中的数据类型依据某种映射关系进行转化,比方 JSON 转成你 Swift 中自界说的数据类型。
HandyJSON 在 Codable 推出后已经不再进行维护了,而咱们的项目便是依赖于 HandyJSON 来处理 JSON 的序列化和反序列化,所以咱们也需求逐渐迁移到 Codable 以防止一些过错。
运用技巧
1.基本运用
struct Person: Codable {
let name: String
let age: Int
}
// 解码
let json = #" {"name":"Tom", "age": 2} "#
let person = try JSONDecoder().decode(Person.self, from: json.data(using: .utf8)!)
print(person) //Person(name: "Tom", age: 2
// 编码
let data0 = try? JSONEncoder().encode(person)
let dataObject = try? JSONSerialization.jsonObject(with: data0!, options: [])
print(dataObject ?? "nil") //{ age = 2; name = Tom; }
let data1 = try? JSONSerialization.data(withJSONObject: ["name": person.name, "age": person.age], options: [])
print(String(data: data1!, encoding: .utf8)!) //{"name":"Tom","age":2}
2.字段映射
struct Person: Codable {
let name: String
let age: Int
let countryName: String
private enum CodingKeys: String, CodingKey {
case countryName = "country"
case name
case age
}
}
let json = #" {"name":"Tom", "age": 2, "country": "China"} "#
let person = try JSONDecoder().decode(Person.self, from: json.data(using: .utf8)!)
print(person) //Person(name: "Tom", age: 2, countryName: "China")
在 Swift 4.1 后,JSONDecoder
增加了 keyDecodingStrategy
特点,假如后端运用带下划线的,蛇形命名法,经过将 keyDecodingStrategy
特点的值设置为 convertFromSnakeCase
,这样就不需求写额外的代码来处理映射了:
var decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
// 后端 person_name
// 映射为 personName
3.解析枚举值
解析枚举值有个坑便是原本比方你只要三个 case
:
enum TestEnum: String {
case a
case b
case c
}
然后服务端某天多加了几个你没有处理的 case
,比方加了个 case d
,那这样去解析就直接崩掉了,所以咱们需求想办法给不知道的 case
设置一个默认值。
具体的完成是这样,增加一个协议:
protocol CodableEnumeration: RawRepresentable, Codable where RawValue: Codable {
static var defaultCase: Self { get }
}
extension CodableEnumeration {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let decoded = try container.decode(RawValue.self)
self = Self.init(rawValue: decoded) ?? Self.defaultCase
} catch {
self = Self.defaultCase
}
}
}
然后在遇到不知道的 case
时就能够经过让 Enum
遵守 CodableEnumeration
,然后完成 defaultCase
办法来指定一个 case
即可。
/**
{
"test_enum" : "d"
}
*/
enum TestEnum: String, CodableEnumeration {
static var defaultCase: TestEnum {
.a
}
case a
case b
case c
}
struct Test: Codable {
var testEnum: TestEnum
private enum CodingKeys: String, CodingKey {
case testEnum = "test_enum"
}
}
let testJson = #" {"test_enum":"d"} "#
let test = try JSONDecoder().decode(Test.self, from: testJson.data(using: .utf8)!)
print(test.testEnum) // a
4.解析嵌套类型
Swift4 支持条件一致性,所以当数组中的每个元素都遵照 Codable 协议,字典中对应的 key
和 value
遵照 Codable 协议,全体目标就遵照 Codable 协议,便是确保你嵌套的类型都遵照 Codable 协议即可。
struct Student: Codable {
var id: String
var name: String
var grade: Int
}
struct Class: Codable {
var classNumber: String
var students: [Student]
}
/**
{
"classNumber": "111",
"students": [{
"id": "1",
"name": "studentA",
"grade": 1
}, {
"id": "2",
"name": "studentB",
"grade": 2
}]
}
*/
let classJson = #"{"classNumber":"111","students":[{"id":"1","name":"studentA","grade":1},{"id":"2","name":"studentB","grade":2}]}"#
let classModel = try JSONDecoder().decode(Class.self, from: classJson.data(using: .utf8)!)
print(classModel)
/** 输出
▿ Class
- classNumber : "111"
▿ students : 2 elements
▿ 0 : Student
- id : "1"
- name : "studentA"
- grade : 1
▿ 1 : Student
- id : "2"
- name : "studentB"
- grade : 2
*/
5.解析空值,为null时指定默认值
后端的接口回来的数据可能有值也可能是为空的,这个时候能够将特点设置为可选类型:
/**
{
"name": "xiaoming",
"age": 18,
"partner": null
}
*/
struct Person: Codable {
let name: String
let age: Int
let partner: String?
}
let personJson = #"{"name":"xiaoming","age":18,"partner":null}"#
let person = try JSONDecoder().decode(Person.self, from: personJson.data(using: .utf8)!)
print(person) // Person(name: "xiaoming", age: 18, partner: nil)
假如不想运用可选类型,能够重写 init(from decoder: Decoder) throws
办法,来指定一个默认的值:
struct Person: Codable {
var name: String
var age: Int
var partner: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
age = try container.decode(Int.self, forKey: .age)
name = try container.decode(String.self, forKey: .name)
partner = try container.decodeIfPresent(String.self, forKey: .partner) ?? ""
}
}
let personJson = #"{"name":"xiaoming","age":18,"partner":null}"#
let person = try JSONDecoder().decode(Person.self, from: personJson.data(using: .utf8)!)
print(person) // Person(name: "xiaoming", age: 18, partner: "")
现在只要三个特点,假如特点很多,那写起来就非常费事,好在咱们有更好的计划:
Annotating properties with default decoding values
简略来说便是经过 @propertyWrapper
来进行优化,在需求默认值的特点上对这个特点进行声明,编译器就能够主动帮助咱们完成赋默认值的操作,当然,这个特点包裹的完成要咱们自己去完成,代码我就直接贴出来,新建个类放到项目中即可:
protocol DecodableDefaultSource {
associatedtype Value: Decodable
static var defaultValue: Value { get }
}
enum DecodableDefault {}
extension DecodableDefault {
@propertyWrapper
struct Wrapper<Source: DecodableDefaultSource> {
typealias Value = Source.Value
var wrappedValue = Source.defaultValue
}
}
extension DecodableDefault.Wrapper: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self)
}
}
extension KeyedDecodingContainer {
func decode<T>(_ type: DecodableDefault.Wrapper<T>.Type,
forKey key: Key) throws -> DecodableDefault.Wrapper<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
extension DecodableDefault {
typealias Source = DecodableDefaultSource
typealias List = Decodable & ExpressibleByArrayLiteral
typealias Map = Decodable & ExpressibleByDictionaryLiteral
enum Sources {
enum True: Source { static var defaultValue: Bool { true } }
enum False: Source { static var defaultValue: Bool { false } }
enum EmptyString: Source { static var defaultValue: String { "" } }
enum EmptyList<T: List>: Source { static var defaultValue: T { [] } }
enum EmptyMap<T: Map>: Source { static var defaultValue: T { [:] } }
enum Zero: Source { static var defaultValue: Int { 0 } }
}
}
extension DecodableDefault {
typealias True = Wrapper<Sources.True>
typealias False = Wrapper<Sources.False>
typealias EmptyString = Wrapper<Sources.EmptyString>
typealias EmptyList<T: List> = Wrapper<Sources.EmptyList<T>>
typealias EmptyMap<T: Map> = Wrapper<Sources.EmptyMap<T>>
typealias Zero = Wrapper<Sources.Zero>
}
extension DecodableDefault.Wrapper: Equatable where Value: Equatable {}
extension DecodableDefault.Wrapper: Hashable where Value: Hashable {}
extension DecodableDefault.Wrapper: Encodable where Value: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
假如需求增加更多的默认值,在 Sources 中增加即可,上面的 Zero
便是额外加的,能够验证一下:
/**
{
"name":null,
"money":null,
"skills":null,
"teachers":null
}
*/
struct Person: Codable {
@DecodableDefault.EmptyString var name: String
@DecodableDefault.Zero var money: Int
@DecodableDefault.EmptyList var skills: [String]
@DecodableDefault.EmptyList var teachers: [Teacher]
}
struct Teacher: Codable {
@DecodableDefault.EmptyString var name: String
}
let personJson = #"{"name":null,"money":null,"skills":null,"teachers":null}"#
let person = try JSONDecoder().decode(Person.self, from: personJson.data(using: .utf8)!)
print(person)
/** 输出
▿ Person
▿ _name : Wrapper<EmptyString>
- wrappedValue : ""
▿ _money : Wrapper<Zero>
- wrappedValue : 0
▿ _skills : Wrapper<EmptyList<Array<String>>>
- wrappedValue : 0 elements
▿ _teachers : Wrapper<EmptyList<Array<Teacher>>>
- wrappedValue : 0 elements
*/
6.编码
取 5 中的 person
,对它进行编码,并输出字符串或转成对应的数据格式:
// 字符串
do {
let jsonData = try JSONEncoder().encode(person)
let jsonString = String(decoding: jsonData, as: UTF8.self)
print(jsonString) // {"money":0,"teachers":[],"name":"","skills":[]}
} catch {
print(error.localizedDescription)
}
// 字典
do {
let jsonData = try JSONEncoder().encode(person)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData) as? [String : Any]
print(jsonObject ?? "null")
} catch {
print(error.localizedDescription)
}
小结
大约常用的操作便是这些,运用起来仍是很方便的,HandyJSON 是依赖于 Swift 的 Runtime 源码揣度内存规矩,假如规矩改变,那么 HandyJSON 就不管用了,而 HandyJSON 现在也不再进行维护,所以在规矩没有改变之前,咱们需求逐渐弃用 HandyJSON 改用原生的 Codable。
参阅
Annotating properties with default decoding values
Encoding and Decoding Custom Types