This is the 8th day of my participation in the August More Text Challenge. For details, see:August is more challenging
Swift-coadable Source code parsing
Common use Codable
Nested models
struct LGTeacher: Codable{
var name: String
var className: String
var courceCycle: Int
var personInfo: PersonInfo
}
extension LGTeacher {
struct PersonInfo: Codable {
var age: Int
var height: Double}}let jsonString = "" "{" name ":" Kody, "" className" : "Swift", "courceCycle" : 10, "personInfo" : {" age ": 18," height ": 1.85}}" ""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder(a)if let data = jsonData{
let result = try? decoder.decode(LGTeacher.self, from: data)
print(result ?? "Parsing failed")}Copy the code
JSON data contains arrays
struct LGTeacher: Codable{
var name: String
var className: String
var courceCycle: Int
var personInfo: [PersonInfo]}extension LGTeacher {
struct PersonInfo: Codable {
var age: Int
var height: Double}}let jsonString = "" "{" name ":" Kody, "" className" : "Swift", "courceCycle" : 10, "personInfo" : [{" age ": 18," height ": 1.85}, {" age" : 20, "height": 1.75}]} ""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder(a)if let data = jsonData{
let result = try? decoder.decode(LGTeacher.self, from: data)
print(result ?? "Parsing failed")}Copy the code
JSON data is a collection of arrays
struct LGTeacher: Codable{
var name: String
var className: String
var courceCycle: Int
}
let jsonString = "" "[{" name ":" Kody ", "className" : "Swift", "courceCycle" : 12}, {" name ":" Cat ", "className" : "training classes", "courceCycle" : 15}, {" name ":" Hank ", "className" : "reverse class", "courceCycle" : 22}, {" name ":" Cooci ", "className" : "master class", "courceCycle" : 22}] "" "
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder(a)if let data = jsonData{
let result = try? decoder.decode([LGTeacher].self, from: data)
print(result ?? "Parsing failed")}Copy the code
JSON dataOptional values
支那
let jsonString = "" "[{" name ":" Kody ", "className" : "Swift", "courceCycle" : 12}, {" name ":" Cat ", "className" : "training classes", "courceCycle" : 15},{"name": "Hank", "className": null, "courceCycle": 22},{"name": "Cooci", "className": "courceCycle": 22},{"name": "Cooci", "className": "courceCycle": 22},{"name": "Cooci", "className": "courceCycle": 22}] "" "
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder(a)if let data = jsonData{
let result = try? decoder.decode([LGTeacher].self, from: data)
print(result ?? "Parsing failed")}Copy the code
A tuple type
For example, if we have a coordinate, location: [20, 10], when we are parsing in Codable areas we need to do the following:
struct Location: Codable {
var x: Double
var y: Double
init(from decoder: Decoder) throws{
var contaioner = try decoder.unkeyedContainer()
self.x = try contaioner.decode(Double.self)
self.y = try contaioner.decode(Double.self)}}struct RawSeverResponse: Codable{
var location: Location
}
let jsonString = """ { "location": [20, 10] } """
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder(a)let result = try decoder.decode(RawSeverResponse.self, from: jsonData!)
print(result.location.x)
Copy the code
Nested data models
inheritance
class LGTeacher: Codable {
var name: String?
}
class LGPartTimeTeacher: LGTeacher {
var partTime: Int?
}
let jsonString = """ { "name": "Kody", "partTime": 20 } """
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder(a)let result = try decoder.decode(LGPartTimeTeacher.self, from: jsonData!)
print(result.name)
Copy the code
protocol LGTeacher {
var name: String{ get set}}//
struct LGPartTimeTeacher: LGTeacher.Codable {
var name: String
var partTime: Int?
}
//
//
let jsonString = """ { "name": "Kody", "partTime": 20 } """
//
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder(a)let result = try decoder.decode(LGPartTimeTeacher.self, from: jsonData!)
print(result)
Copy the code
Inconvenient array type
struct LGPerson: Decodable{
let elements: [String]
enum CodingKeys: String.CaseIterable.CodingKey {
case item0 = "item.0"
case item1 = "item.1"
case item2 = "item.2"
case item3 = "item.3"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var element: [String] = []
for item in CodingKeys.allCases{
guard container.contains(item) else { break }
element.append(try container.decode(String.self, forKey: item))
}
self.elements = element
}
}
//
//
let jsonString = """ { "item.3": "Kody", "item.0": "Hank", "item.2": "Cooci", "item.1": "Cat" } """
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder(a)let result = try decoder.decode(LGPerson.self, from: jsonData!)
print(result)
Copy the code
Codable source parsing
Decoding process
Let’s look at it firstCodable
What is it?
Here’s a simple example:
struct LGTeacher: Codable {
var name: String
var className: String
var courceCycle: Int
}
let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10 } """
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder(a)if let data = jsonData{
let result = try? decoder.decode(LGTeacher.self, from: data)
print(result ?? "Parsing failed")}Copy the code
Currently we create a decoded object and then call the decode method to parse our JSON string to our model LGTeacher. What we need to explore here is how exactly does it work? First, let’s take a look at the objects created by JSONDecoder
public enum DateDecodingStrategy {
/// Defer to `Date` for decoding. This is the default strategy.
case deferredToDate
/// represents the number of seconds from 1970.01.01
/// Decode the `Date` as a UNIX timestamp from a JSON number.
case secondsSince1970
/// represents the number of milliseconds from 1970.1.1
/// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
case millisecondsSince1970
/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
@available(OSX 10.12.iOS 10.0.watchOS 3.0.tvOS 10.0.*)
case iso8601
/// Create a DateFormatter to parse the format
/// Decode the `Date` as a string parsed by the given formatter.
case formatted(DateFormatter)
/// Customize the format
/// Decode the `Date` as a custom value decoded by the given closure.
case custom((Decoder) throws -> Date)}Copy the code
Here’s a look at a practical usage scenario:
struct LGTeacher: Codable {
var name: String
var className: String
var courceCycle: Int
var date: Date
}
let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10 "date": "1969-09-26T12:00:00Z" } """
Copy the code
If we had just used the default parsing strategy, we would have had a parsing failure after the code had run
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.iso8601
Copy the code
let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10, "date": 1609183207 } """
Copy the code
decoder.dateDecodingStrategy = .secondsSince1970
Copy the code
let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10, "date": 1609183207000 } """
decoder.dateDecodingStrategy = .millisecondsSince1970
Copy the code
let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10, "date": "2020/12/28 19:20:00" } """This background custom format requires us to create oneDateFormatter
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
Copy the code
The above isJSONDecoder
The main content of the code defines the encoding strategy, which allows us to choose according to different scenarios.
Now let’s actually look at the current situationdecode
的
- Here is a generic function, passed in as an argument
T
Required to followDecodable
The agreement. - call
JSONSerializationg
For the currentdata
Perform the operation of sequence words - Calling inner class
_JSONDecoder
Create an object and then callunBox
decoding
Let’s focus on step 3 and Step 4 here, starting with step 3: One is returnedDecoder
The object.
Among themstorage
The implementation of the:
Going back to our fourth step,unBox
Just start unpacking the box
You can see that this is matching the corresponding type and then executing the conditional branch
Back to our currentmain.swift
At this point we didn’t implement any methods
So what’s going on here? We turned to our old friendsSIL
Take a look:
This means that the current compiler automatically generates a defaultinit(from:)
The implementation. Let’s read what happened here:
What’s the protocol method of finding? Let’s go back to that_JSONDecoder
The implementation of the:
Let’s compare:
This is what we found by calling the current one againcontainer
Method implementation; And where is this method implemented? Is that where_JSONDecoder
Inside, let’s look at the next break point
And then the question is, how do we iterate through our currentKey
Valuekey-value
Assignment operation of? First of all, let’s seeSIL
file
The enumeration type is first created in memoryname
, and then callDecode ()
Method. At this point we’re going back to our
findKeyedDecodingContainer
Concrete implementation of:
At this timeContainer
What is? Is that what it is
Here,Decode
Methods are_box
Let’s go back and look for it again
So basically at this point we see that we’re actually calling_JSONKeyedDecodingContainer
thedecode
Method, here we directly use the breakpoint to determine:
Coding flow processing
Analysis of Codable pits
In the case above, one of the cases that we talked about at the beginning of the course is inheritance. So if we modify the case:
class LGPerson: Codable {
var name: String?
var age: Int?
}
class LGTeacher: LGPerson {
var subjectName: String?
}
class LGPartTimeTeacher: LGPerson{
var partTime: Double?
}
let t = LGTeacher()
t.age = 10
t.name = "Kody"
t.subjectName = "Swift"
let encoder = JSONEncoder(a)let encoderData = try encoder.encode(t)
print(String(data: encoderData, encoding: .utf8))
Copy the code
Let’s take a look at the above case to see if the code is successful
You can see that currently only normal code succeeds ourage
和 name
But wesubjectName
It doesn’t code properly. Here we godebug
Look at the current why?
Our currenttype
是 LGTeacher
Did not complyif
So the current code executes to the following branch:
That hereencode
Methods are followed by usCodable
After the protocol, the system automatically helps us implement, we passSIL
Take a look:
Let’s see againLGPerson
The default implementation, so naturally we don’t have access to the current oneLGPerson
The code will only be correctage
, name
It’s coded.
class LGTeacher: LGPerson {
var subjectName: String?
// init(name: String, age: Int, subjectName: String) {
// self.subjectName = subjectName
// super.init(name: name, age: age)
/ /}
//
// required init(from decoder: Decoder) throws {
// fatalError("init(from:) has not been implemented")
/ /}
// enum CodingKeys: String, CodingKey{
// case subjectName
/ /}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(subjectName, forKey: .subjectName)
let superdecoder = container.superEncoder()
try super.encode(to: superdecoder)
}
}
Copy the code
However, there is another problem with writing this way, because the current CodingKeys are not accessible, so we need to do this here
class LGTeacher: LGPerson {
var subjectName: String?
// init(name: String, age: Int, subjectName: String) {
// self.subjectName = subjectName
// super.init(name: name, age: age)
/ /}
//
// required init(from decoder: Decoder) throws {
// fatalError("init(from:) has not been implemented")
/ /}
enum CodingKeys: String.CodingKey{
case subjectName
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(subjectName, forKey: .subjectName)
let superdecoder = container.superEncoder()
try super.encode(to: superdecoder)
}
}
Copy the code
So we can code it correctly
If we change the code one more time:
class LGPerson: Codable {
var name: String?
var age: Int?
init(name: String.age: Int) {
self.age = age
self.name = name
}
}
class LGTeacher: LGPerson {
var subjectName: String?
init(name: String.age: Int.subjectName: String) {
self.subjectName = subjectName
super.init(name: name, age: age)
}
required init(from decoder: Decoder) throws {
fatalError("init(from:) has not been implemented")}enum CodingKeys: String.CodingKey{
case subjectName
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(subjectName, forKey: .subjectName)
let superdecoder = container.superEncoder()
try super.encode(to: superdecoder)
}
}
class LGPartTimeTeacher: LGPerson{
var partTime: Double?
}
let t: LGPerson = LGTeacher.init(name: "Kody", age: 10, subjectName: "Swift")
let encoder = JSONEncoder(a)let encoderData = try encoder.encode(t)
print(String(data: encoderData, encoding: .utf8))
let t1: LGPerson = try JSONDecoder().decode(LGTeacher.self, from: encoderData)
Copy the code
You can see here, it’s just an error:
It looks like we didn’t implement thisdecoder
Then let’s implement it:
required init(from decoder: Decoder) throws {
// fatalError("init(from:) has not been implemented")
let container = try decoder.container(keyedBy: CodingKeys.self)
self.subjectName = try container.decode(String.self, forKey: .subjectName)
try super.init(from: decoder)
}
Copy the code
let t1: LGPerson = try JSONDecoder().decode(LGTeacher.self, from: encoderData)
print(t1.age)
print(t1.name)
Copy the code
And if the current property is not optional, then we have a crashed application directly here.
If we replace it with a structure here, is the result the same? Let’s test it out first:
protocol LGPerson: Codable {
var age: String { get set }
var name: String { get set}}struct LGTeacher: LGPerson {
var age: String
var name: String
}
struct LGParTimeTeacher: LGPerson {
var age: String
var name: String
}
struct Company: Codable{
var person: [LGPerson]
var companyName: String
enum CodingKeys: String.CodingKey {
case person
case companyName
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(person, forKey: .person)
try container.encode(companyName, forKey: .companyName)
}
}
Copy the code
The current compiler will just report an error, because the currentLGPerson
It’s an agreement. He can’t keep it
So what do we do? At this point we’re probably thinking about the directLGTeacher
, LGPartTimeTeacher
In the implementationDecode and encode
Then we can find an intermediate layer to solve the problem:
protocol LGPerson{
var age: String { get set }
var name: String { get set}}struct LGTeacher: LGPerson {
var age: String
var name: String
}
struct LGParTimeTeacher: LGPerson {
var age: String
var name: String
}
struct LGPersonBox : LGPerson.Codable {
var age: String
var name: String
init(_ person: LGPerson) {
self.age = person.age
self.name = person.name
}
}
struct Company: Codable{
var person: [LGPersonBox]
var companyName: String
}
Copy the code
protocol LGPerson{
var age: Int { get set }
var name: String { get set}}struct LGTeacher: LGPerson {
var age: Int
var name: String
}
struct LGParTimeTeacher: LGPerson {
var age: Int
var name: String
}
struct LGPersonBox : LGPerson.Codable {
var age: Int
var name: String
init(_ person: LGPerson) {
self.age = person.age
self.name = person.name
}
}
struct Company: Codable{
var person: [LGPersonBox]
var companyName: String
}
let person: [LGPerson] = [LGTeacher(age: 20, name: "Kody"),LGParTimeTeacher(age: 30, name: "Hank")]
let company = Company(person: person.map(LGPersonBox.init), companyName: "Logic")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(company)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
Copy the code
You can see that the format of the output isLGPersonBox
What should we do if we want to restore our current type information correctly? A very simple way to do that is to encode our type information in the coding process. What does that mean? Let’s look at it in the code:
enum LGPersonType:String.Codable {
case teacher
case partTeacher
var metdadata: LGPerson.Type {
switch self {
case .teacher:
return LGTeacher.self
case .partTeacher:
return LGParTimeTeacher.self}}}protocol LGPerson: Codable{
static var type: LGPersonType{ get }
var age: Int { get set }
var name: String { get set}}struct LGTeacher: LGPerson {
static var type: LGPersonType = LGPersonType.teacher
var age: Int
var name: String
}
struct LGParTimeTeacher: LGPerson {
static var type: LGPersonType = LGPersonType.partTeacher
var age: Int
var name: String
}
struct LGPersonBox : Codable {
var p: LGPerson
init(_ p: LGPerson) {
self.p = p
}
private enum CodingKeys : CodingKey {
case type
case p
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(LGPersonType.self, forKey: .type)
self.p = try type.metdadata.init(from: container.superDecoder(forKey: .p))
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type(of: p).type, forKey: .type)
try p.encode(to: container.superEncoder(forKey: .p))
}
}
struct Company: Codable{
var person: [LGPersonBox]
var companyName: String
}
let person: [LGPerson] = [LGTeacher(age: 20, name: "Kody"),LGParTimeTeacher(age: 30, name: "Hank")]
let company = Company(person: person.map(LGPersonBox.init), companyName: "Logic")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(company)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
Copy the code
And of course if we want to output this one down here
New implementation
protocol Meta: Codable {
associatedtype Element
static func metatype(for typeString: String) -> Self
var type: Decodable.Type { get}}struct MetaObject<M: Meta> :Codable {
let object: M.Element
init(_ object: M.Element) {
self.object = object
}
enum CodingKeys: String.CodingKey {
case metatype
case object
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let typeStr = try container.decode(String.self, forKey: .metatype)
let metatype = M.metatype(for: typeStr)
let superDecoder = try container.superDecoder(forKey: .object)
let obj = try metatype.type.init(from: superDecoder)
guard let element = obj as? M.Element else {
fatalError()}self.object = element
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let typeStr = String(describing: type(of: object))
try container.encode(typeStr, forKey: .metatype)
let superEncoder = container.superEncoder(forKey: .object)
let encodable = object as? Encodable
try encodable?.encode(to: superEncoder)
}
}
enum LGPersonType: String.Meta {
typealias Element = LGPerson
case teacher = "LGTeacher"
case partTimeTeacher = "LGPartTimeTeacher"
static func metatype(for typeString: String) -> LGPersonType {
guard let metatype = self.init(rawValue: typeString) else {
fatalError()}return metatype
}
var type: Decodable.Type {
switch self {
case .teacher:
return LGTeacher.self
case .partTimeTeacher:
return LGPartTimeTeacher.self}}}class LGPerson: Codable {
var name: String
var age: Int
init(name: String.age: Int) {
self.name = name
self.age = age
}
}
class LGTeacher: LGPerson {
var subjectName: String
init(name: String.age: Int.subjectName: String) {
self.subjectName = subjectName
super.init(name: name, age: age)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
subjectName = try container.decode(String.self, forKey: .subjectName)
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(subjectName, forKey: .subjectName)
let superdecoder = container.superEncoder()
try super.encode(to: superdecoder)
}
enum CodingKeys: String.CodingKey {
case subjectName
}
}
class LGPartTimeTeacher: LGPerson {
var partTime: Double
init(name: String.age: Int.partTime: Double) {
self.partTime = partTime
super.init(name: name, age: age)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
partTime = try container.decode(Double.self, forKey: .partTime)
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(partTime, forKey: .partTime)
let superdecoder = container.superEncoder()
try super.encode(to: superdecoder)
}
enum CodingKeys: String.CodingKey {
case partTime
}
}
let p: LGPerson = LGTeacher(name: "Kody", age: 20, subjectName: "Swift")
let jsonData = try JSONEncoder().encode(MetaObject<LGPersonType>(p))
if let str = String(data: jsonData, encoding: .utf8) {
print(str)
}
let decode: MetaObject<LGPersonType> = try JSONDecoder().decode(MetaObject<LGPersonType>.self, from: jsonData)
Copy the code