这是Swift数据解析计划的系列文章:

Swift数据解析(第一篇) – 技能选型

Swift数据解析(第二篇) – Codable 上

Swift数据解析(第二篇) – Codable 下

Swift数据解析(第三篇) – Codable源码学习

Swift数据解析(第四篇) – SmartCodable 上

Swift数据解析(第四篇) – SmartCodable 下

七. 派生关系

1. super.encode(to: encoder)

来看一个这样的场景,咱们有一个Point2D的类,包括x和y两个特点,用来标记二维点的方位。 还有继承于Point2D的Point3D,完成三维点定位。

class Point2D: Codable {
 var x = 0.0
 var y = 0.0
}
​
class Point3D: Point2D {
 var z = 0.0
}

1. z 去哪里了?

let point = Point3D()
point.x = 1.0
point.y = 2.0
point.z = 3.0
​
guard let value = point.encode() else { return }
print(value)
​
此刻的打印结果是:
{
"x" : 1,
"y" : 2
}

相信你现已反应过来了:子类没有重写父类的 encode 办法,默许运用的父类的 encode 办法。子类的特点天然没有被 encode

2. x 和 y 的去哪了?

咱们将代码做这样的修正:

class Point2D: Codable {
 var x = 0.0
 var y = 0.0
 
 private enum CodingKeys: CodingKey {
   case x
   case y
 }
 
 func encode(to encoder: Encoder) throws {
   var container = encoder.container(keyedBy: CodingKeys.self)
   try container.encode(self.x, forKey: .x)
   try container.encode(self.y, forKey: .y)
 }
}
​
class Point3D: Point2D {
 var z = 0.0enum CodingKeys: CodingKey {
   case z
 }
 
 override func encode(to encoder: Encoder) throws {
   var container = encoder.container(keyedBy: CodingKeys.self)
   try container.encode(self.z, forKey: .z)
 }
}
​
此刻的打印结果是:
{
"z" : 3
}

相信你应该十分清楚这个原因:子类重写了父类的 decode 完成,导致父类的encode没有履行。

3. x, y 和 z 都在了

class Point3D: Point2D {
 var z = 0.0enum CodingKeys: CodingKey {
   case z
 }
 
 override func encode(to encoder: Encoder) throws {
   try super.encode(to: encoder)

   var container = encoder.container(keyedBy: CodingKeys.self)
   try container.encode(self.z, forKey: .z)
 }
}
​
此刻的打印结果是:
{
"x" : 1,
"y" : 2,
"z" : 3
}

在子类的 encode 办法里边调用了父类的 encode 办法,完成了子类和父类的特点编码。

2. super.init(from: decoder)

再将打印的值解码成模型。

class Point2D: Codable {
 var x = 0.0
 var y = 0.0
 
 enum CodingKeys: CodingKey {
   case x
   case y
 }
 
 required init(from decoder: Decoder) throws {
   let container = try decoder.container(keyedBy: CodingKeys.self)
   self.x = try container.decode(Double.self, forKey: .x)
   self.y = try container.decode(Double.self, forKey: .y)
 }
}
​
class Point3D: Point2D {
 var z = 0.0

 enum CodingKeys: CodingKey {
   case z
   case point2D
 }
 
 required init(from decoder: Decoder) throws {
   let container = try decoder.container(keyedBy: CodingKeys.self)
   self.z = try container.decode(Double.self, forKey: .z)
   
   try super.init(from: decoder)
 }
}
​
let json = """
{
"x" : 1,
"y" : 2,
"z" : 3
}
"""guard let point = json.decode(type: Point3D.self) else { return }
print(point.x)
print(point.y)
print(point.z)
​
此刻的打印结果是:
1.0
2.0
3.0

3. superEncoder & superDecoder

上面的案例中,父类和子类同享一个 container,这不利于咱们咱们差异。运用superEncoder创立新的容器:

class Point3D: Point2D {
 var z = 0.0enum CodingKeys: CodingKey {
   case z
 }
 
 override func encode(to encoder: Encoder) throws {
   var container = encoder.container(keyedBy: CodingKeys.self)
   try container.encode(self.z, forKey: .z)
   
   let superEncoder = container.superEncoder()
   try super.encode(to: superEncoder)
 }
}
​
输出信息是:
{
"z" : 3,
"super" : {
 "x" : 1,
 "y" : 2
}
}

同样咱们运用superDecoder用来编码:

required init(from decoder: Decoder) throws {
 let container = try decoder.container(keyedBy: CodingKeys.self)
 self.z = try container.decode(Double.self, forKey: .z)
 try super.init(from: container.superDecoder())
}
​
输出信息是:
1.0
2.0
3.0
了解输出信息中的 super 的意义

咱们来看一个示例:这是一个班级的信息,其包括班级号,班长,学生成员的信息。

struct Student: Encodable {
 var name: String
 enum CodingKeys: CodingKey {
   case name
 }
}
​
struct Class: Encodable {
 var numer: Int
 var monitor: Student
 var students: [Student]
 
 init(numer: Int, monitor: Student, students: [Student]) {
   self.numer = numer
   self.monitor = monitor
   self.students = students
 }
}
​
let monitor = Student(name: "小明")
let student1 = Student(name: "大黄")
let student2 = Student(name: "小李")
var classFeed = Class(numer: 10, monitor: monitor, students: [student1, student2])
​
guard let value = classFeed.encode() else { return }
print(value)
​
// 输出信息是:
{
"numer" : 10,
"monitor" : {
 "name" : "小明"
},
"students" : [
 {
  "name" : "大黄"
 },
 {
  "name" : "小李"
 }
]
}

重写Class类的encode办法:

func encode(to encoder: Encoder) throws {
 
}
​
// 报错信息:尖端类没有编码任何值
Swift.EncodingError.Context(codingPath: [], debugDescription: "Top-level Class did not encode any values.", underlyingError: nil)

只创立容器:

func encode(to encoder: Encoder) throws {
 var container = encoder.container(keyedBy: CodingKeys.self)
}
​
// 输出信息是:
{
​
}

当时container的superEncoder

enum CodingKeys: CodingKey {
 case numer
 case monitor
 case students
 
 // 新增一个key
 case custom
}
​
func encode(to encoder: Encoder) throws {
 var container = encoder.container(keyedBy: CodingKeys.self)
 
 var superEncoder = container.superEncoder()
}
​
// 输出信息是:
{
"super" : {
​
}
}

键值编码容器 和 无键编码容器的下 的差异

func encode(to encoder: Encoder) throws {
 var container = encoder.container(keyedBy: CodingKeys.self)
 
 // 在monitor字典容器中
 var monitorContainer = container.nestedContainer(keyedBy: Student.CodingKeys.self, forKey: .monitor)
 // 在monitor容器下,新生成一个编码器
 let monitorSuperEncoder = monitorContainer.superEncoder()
​
 
 // 在students数组容器中
 var studentsContainer = container.nestedUnkeyedContainer(forKey: .students)
 for student in students {
   let studentsSuperEncoder = studentsContainer.superEncoder()
 }
}
​
// 打印信息
{
"monitor" : {
 "super" : {
​
 }
},
"students" : [
 {
​
 },
 {
​
 }
]
}

相信你现已体会到 super 的意义了。

在当时层级下收效: 运用superEncoder 或 superDecoder 在 当时层级下 生成一个新的Encoder 或 Decoder。

由调用者 container 决议生成的结构

  • 假如是 KeyedEncodingContainer 对应的是字典类型,构成 { super: { } } 这样的结构。
  • 假如是 UnkeyedEncodingContainer 对应的是数组结构。
  • SingleValueEncodingContainer 没有 super。

4. 指定编码父类的key

体系还供给一个办法:

public mutating func superEncoder(forKey key: KeyedEncodingContainer<K>.Key) -> Encoder

咱们能够经过调用指定key的办法,创立一个新的编码器:

class Point3D: Point2D {
 var z = 0.0enum CodingKeys: CodingKey {
   case z
   case point2D
 }
 
 override func encode(to encoder: Encoder) throws {
   var container = encoder.container(keyedBy: CodingKeys.self)
   try container.encode(self.z, forKey: .z)
   
   let superEncoder = container.superEncoder(forKey: .point2D)
   try super.encode(to: superEncoder)
 }
}
​
// 输出信息是:
{
"z" : 3,
"point2D" : {
 "x" : 1,
 "y" : 2
}
}

当然咱们也能够经过自定义CodingKey完成指定Key:

class Point2D: Codable {
 var x = 0.0
 var y = 0.0

 struct CustomKeys: CodingKey {
   var stringValue: String
   
   init?(stringValue: String) {
     self.stringValue = stringValue
   }
   
   init(_ stringValue: String) {
     self.stringValue = stringValue
   }
   
   var intValue: Int?
   
   init?(intValue: Int) {
     self.stringValue = ""
   }
 }
}
​
class Point3D: Point2D {
 var z = 0.0enum CodingKeys: CodingKey {
   case z
 }
 
 override func encode(to encoder: Encoder) throws {
   var container = encoder.container(keyedBy: CustomKeys.self)
   try container.encode(self.z, forKey: CustomKeys.init("z"))
   
   let superEncoder = container.superEncoder(forKey: CustomKeys("point2D"))
   try super.encode(to: superEncoder)
 }
}

八. 枚举

遵从Codable协议的枚举,也能够自动将数据转化为枚举值。

struct EnumFeed: Codable {
 var a: SexEnum
 var b: SexEnum
}
​
enum SexEnum: String, Codable {
 case man
 case women
}
​
let json = """
{
 "a": "man",
 "b": "women"
}
"""
​
guard let feed = json.decode(type: EnumFeed.self) else { return }
print(feed

1. 枚举映射失利的异常

假如未被枚举的值呈现(将数据中b的值改为 “unkown”),decode的时分会抛出DecodingError。 哪怕声明为可选,一样会报错。

 DecodingError
 dataCorrupted : Context
  codingPath : 1 element
  - 0 : CodingKeys(stringValue: "b", intValue: nil)
 - debugDescription : "Cannot initialize SexEnum from invalid String value unkown"
 - underlyingError : nil

2. 兼容计划

重写init办法,自定义映射

struct EnumFeed: Codable {
 var a: SexEnum
 var b: SexEnum?
 
 init(from decoder: Decoder) throws {
   let container = try decoder.container(keyedBy: CodingKeys.self)
   self.a = try container.decode(SexEnum.self, forKey: .a)
   
   if try container.decodeNil(forKey: .b) {
     self.b = nil
   } else {
     let bRawValue = try container.decode(String.self, forKey: .b)
     if let temp = SexEnum(rawValue: bRawValue) {
       self.b = temp
     } else {
       self.b = nil
     }
   }
 }
}

运用协议供给映射失利的默许值

苹果官方给了一个解决办法: 运用协议供给映射失利的默许值

public protocol SmartCaseDefaultable: RawRepresentable, Codable {
 /// 运用接收到的数据,无法用枚举类型中的任何值表示而导致解析失利,运用此默许值。
 static var defaultCase: Self { get }
}
​
public extension SmartCaseDefaultable where Self.RawValue: Decodable {
 init(from decoder: Decoder) throws {
   let container = try decoder.singleValueContainer()
   let rawValue = try container.decode(RawValue.self)
   self = Self.init(rawValue: rawValue) ?? Self.defaultCase
 }
}

使此枚举继承本协议即可

enum SexEnum: String, SmartCaseDefaultable {
 case man
 case women
 
 static var defaultCase: SexEnum = .man
}

九. 特别格局的数据

1. 日期格局

let json = """
{
 "birth": "2000-01-01 00:00:01"
}
"""
guard let data = json.data(using: .utf8) else { return }
let decoder = JSONDecoder()
​
​
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
do {
 let feed = try decoder.decode(Person.self, from: data)
 print(feed)
} catch {
 print("Error decoding: (error)")
}
​
struct Person: Codable {
 var birth: Date
}

更多关于dateDecodingStrategy的运用请检查 DateDecodingStrategy

public enum DateDecodingStrategy : Sendable {
​
 case deferredToDate
​
 case secondsSince1970
​
 case millisecondsSince1970
​
 case iso8601

 case formatted(DateFormatter)@preconcurrency case custom(@Sendable (_ decoder: Decoder) throws -> Date)
}

2. 浮点数

let json = """
{
 "height": "NaN"
}
"""
​
struct Person: Codable {
 var height: Float
}
​
guard let data = json.data(using: .utf8) else { return }
let decoder = JSONDecoder()
decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "+∞", negativeInfinity: "-∞", nan: "NaN")
do {
 let feed = try decoder.decode(Person.self, from: data)
 print(feed.height)
} catch {
 print("Error decoding: (error)")
}
// 输出: nan

当Float类型遇到 NaN时分,如不做特别处理,会导致解析失利。能够运用nonConformingFloatDecodingStrategy 兼容不符合json浮点数值当情况。

更多信息请检查:

public enum NonConformingFloatDecodingStrategy : Sendable {
 case `throw`
 case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
}

3. Data格局

有一个这样的base64的数据

let json = """
{
 "address": "aHR0cHM6Ly93d3cucWl4aW4uY29t"
}
"""
struct QXBWeb: Codable {
 var address: Data
}
​
guard let data = json.data(using: .utf8) else { return }
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64
do {
 let web = try decoder.decode(QXBWeb.self, from: data)
 guard let address = String(data: web.address, encoding: .utf8) else { return }
 print(address)
} catch {
 print("Error decoding: (error)")
}
// 输出: https://www.qixin.com

更多关于dataDecodingStrategy的信息,请检查DataDecodingStrategy。

public enum DataDecodingStrategy : Sendable {
 case deferredToData
 /// 从base64编码的字符串解码' Data '。这是默许战略。
 case base64
 @preconcurrency case custom(@Sendable (_ decoder: Decoder) throws -> Data)
}

4. URL格局

Codable能够自动将字符串映射为URL格局。

struct QXBWeb: Codable {
 var address: URL
}
​
let json = """
{
 "address": "https://www.qixin.com"
}
"""
​
guard let data = json.data(using: .utf8) else { return }
let decoder = JSONDecoder()
do {
 let web = try decoder.decode(QXBWeb.self, from: data)
 print(web.address.absoluteString)
} catch {
 print("Error decoding: (error)")
}

但是要注意 数据为 “”的情况。

 DecodingError
 dataCorrupted : Context
  codingPath : 1 element
  - 0 : CodingKeys(stringValue: "address", intValue: nil)
 - debugDescription : "Invalid URL string."
 - underlyingError : nil

咱们来看体系内部是如何进行解码URL的:

if T.self == URL.self || T.self == NSURL.self {
 guard let urlString = try self.unbox(value, as: String.self) else {
   return nil
 }
 
 guard let url = URL(string: urlString) else {
   throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath,
                               debugDescription: "Invalid URL string."))
 }
 
 decoded = (url as! T)
}

解码URL分为两步: 1. urlString是否存在。 2. urlString是否能够转成URL。

能够运用这个办法兼容,URL无法供给默许值,只能设置为可选。

struct QXBWeb: Codable {
 var address: URL?
 
 init(from decoder: Decoder) throws {
   let container = try decoder.container(keyedBy: CodingKeys.self)
   do {
     let str = try container.decode(String.self, forKey: .address)
     if let url = URL(string: str) {
       self.address = url
     } else {
       self.address = nil
     }
   } catch {
     print(error)
     self.address = nil
   }
 }
}

十. 关于嵌套结构

除了解码元素,UnkeyedDecodingContainerKeyedDecodingContainer 还供给了一些其他有用的办法,例如nestedContainer(keyedBy:forKey:)nestedUnkeyedContainer(forKey:),用于解码嵌套的容器类型。

  • nestedContainer: 用于生成解码字典类型的容器。
  • nestedUnkeyedContainer: 用于生成解码数组类型的容器。

nestedContainer的运用场景

在Swift中,nestedContainer是一种用于处理嵌套容器的办法。它是KeyedDecodingContainerUnkeyedDecodingContainer协议的一部分,用于解码嵌套的数据结构。

///回来存储在给定键类型的容器中的给定键的数据。
///
/// -参数类型:用于容器的键类型。
/// -参数key:嵌套容器相关的键。
/// -回来:一个KeyedDecodingContainer容器视图。
/// -抛出:' DecodingError. 'typeMismatch ',假如遇到的存储值不是键控容器。
public func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey

当你需求解码一个嵌套的容器时,你能够运用nestedContainer办法。这个办法接受一个键(关于KeyedDecodingContainer)或者一个索引(关于UnkeyedDecodingContainer),然后回来一个新的嵌套容器,你能够运用它来解码嵌套的值。

Swift数据解析(第二篇) - Codable 下

咱们将图左边的数据,解码到图右侧的Class模型中。 Class 模型对应的数据结构如图所示。

咱们先来创立这两个模型: Class 和 Student

struct Student: Codable {
 let id: String
 let name: String
}
​
struct Class: Codable {
 var students: [Student] = []
​
 // 数据中的key结构和模型中key结构不对应,需求自定义Key
 struct CustomKeys: CodingKey {
   var intValue: Int? {return nil}
   var stringValue: String
   init?(intValue: Int) {return nil}
   init?(stringValue: String) {
     self.stringValue = stringValue
   }
   
   init(_ stringValue: String) {
     self.stringValue = stringValue
   }
 }
}

Class是一个模型,所以对应的应该是一个KeyedDecodingContainer容器。

init(from decoder: Decoder) throws {
 let container = try decoder.container(keyedBy: CustomKeys.self)
​
 let keys = container.allKeys
​
 var list: [Student] = []
 for key in keys {
   let nestedContainer = try container.nestedContainer(keyedBy: CustomKeys.self, forKey: key)
   let name = try nestedContainer.decode(String.self, forKey: .init("name"))
   let student = Student(id: key.stringValue, name: name)
​
   list.append(student)
 }
 self.students = list
}

container.allKeys的值为:

3 elements
▿ 0 : CustomKeys(stringValue: "2", intValue: nil)
 - stringValue : "2"1 : CustomKeys(stringValue: "1", intValue: nil)
 - stringValue : "1"2 : CustomKeys(stringValue: "3", intValue: nil)
 - stringValue : "3"

经过遍历allKeys得到的key,对应的数据结构是一个字典: { “name” : xxx }。

let nestedContainer = try container.nestedContainer(keyedBy: CustomKeys.self, forKey: key)
let name = try nestedContainer.decode(String.self, forKey: .init("name"))
let student = Student(id: key.stringValue, name: name)

这样就得Student的悉数数据。id的值便是key.stringValue,name的值便是nestedContainer容器中key为“name”的解码值。

根据这个思路,咱们也能够很简单的完成 encode 办法

func encode(to encoder: Encoder) throws {
 var container = encoder.container(keyedBy: CustomKeys.self)
 
 for student in students {
   var keyedContainer = container.nestedContainer(keyedBy: CustomKeys.self, forKey: .init(student.id))
   try keyedContainer.encode(student.name, forKey: .init("name"))
 }
}

nestedUnkeyedContainer的运用场景

nestedUnkeyedContainer 是 Swift 中的 KeyedDecodingContainer 协议的一个办法,它允许你从嵌套的无键容器中解码一个值的数组,该容器通常是从 JSON 或其他编码数据中获取的。

/// 回来为给定键存储的数据,以无键容器的形式表示。
public func nestedUnkeyedContainer(forKey key: KeyedDecodingContainer<K>.Key) throws -> UnkeyedDecodingContainer

有一个这样的数据结构,解码到 Person 模型中:

letjson="""
{
 "name":"xiaoming",
 "age":10,
 "hobbies":[
   {
     "name":"basketball",
     "year":8
   },
   {
     "name":"soccer",
     "year":2
   }
 ]
}
"""structPerson{
 letname:String
 letage:Int
 varhobbies:[Hobby]
 
 structHobby{
   letname:String
   letyear:Int
 }
}
运用体系自带嵌套解析才能
struct Person: Codable {
 let name: String
 let age: Int
 var hobbies: [Hobby]
 
 struct Hobby: Codable {
   let name: String
   let year: Int
 }
}
运用自定义解码
structPerson:Codable{
 letname:String
 letage:Int
 varhobbies:[Hobby]
 
 structHobby:Codable{
   letname:String
   letyear:Int
   
   enumCodingKeys:CodingKey{
     casename
     caseyear
   }
 }
 
 init(fromdecoder:Decoder)throws{
   letcontainer=trydecoder.container(keyedBy:CodingKeys.self)
   name=trycontainer.decode(String.self,forKey:.name)
   age=trycontainer.decode(Int.self,forKey:.age)
   
   // 解码嵌套的数组
   varnestedContainer=trycontainer.nestedUnkeyedContainer(forKey:.hobbies)
   vartempHobbies:[Hobby]=[]
   while!nestedContainer.isAtEnd{
     iflethobby=try?nestedContainer.decodeIfPresent(Hobby.self){
       tempHobbies.append(hobby)
     }
   }
   hobbies=tempHobbies
 }
}
运用完全自定义解码
structPerson:Codable{
 letname:String
 letage:Int
 varhobbies:[Hobby]
 
 structHobby:Codable{
   letname:String
   letyear:Int
   
   enumCodingKeys:CodingKey{
     casename
     caseyear
   }
 }
 
 init(fromdecoder:Decoder)throws{
   letcontainer=trydecoder.container(keyedBy:CodingKeys.self)
   name=trycontainer.decode(String.self,forKey:.name)
   age=trycontainer.decode(Int.self,forKey:.age)
   
   varhobbiesContainer=trycontainer.nestedUnkeyedContainer(forKey:.hobbies)
   vartempItems:[Hobby]=[]
   while!hobbiesContainer.isAtEnd{
     lethobbyContainer=tryhobbiesContainer.nestedContainer(keyedBy:Hobby.CodingKeys.self)
     
     letname=tryhobbyContainer.decode(String.self,forKey:.name)
     letyear=tryhobbyContainer.decode(Int.self,forKey:.year)
     letitem=Hobby(name:name,year:year)
     tempItems.append(item)
   }
   hobbies=tempItems
 }
}

十一. 其他一些小知识点

1. 模型中特点的didSet办法不会履行

struct Feed: Codable {
 init() {
   
 }
​
 var name: String = "" {
   didSet {
     print("被set了")
   }
 }
 
 init(from decoder: Decoder) throws {
   let container = try decoder.container(keyedBy: CodingKeys.self)
   self.name = try container.decode(String.self, forKey: .name)
 }
}
​
let json = """
{
 "name": "123"
}
"""guard let feed = json.decode(type: Feed.self) else { return }
print(feed)

在Swift中,运用Codable解析完成后,模型中的特点的didSet办法不会履行。

这是因为didSet办法只在特点被直接赋值时触发,而不是在解析过程中。

Codable协议运用编码和解码来将数据转换为模型目标,而不是经过特点的直接赋值来触发didSet办法。

假如您需求在解析完成后履行特定的操作,您能够在解析后手动调用相应的办法或者运用自定义的初始化办法来处理。