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 firstCodableWhat 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 isJSONDecoderThe 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 argumentTRequired to followDecodableThe agreement.
  • callJSONSerializationgFor the currentdataPerform the operation of sequence words
  • Calling inner class_JSONDecoderCreate an object and then callunBoxdecoding


Let’s focus on step 3 and Step 4 here, starting with step 3: One is returnedDecoderThe object.





Among themstorageThe implementation of the:



Going back to our fourth step,unBoxJust start unpacking the box



You can see that this is matching the corresponding type and then executing the conditional branch



Back to our currentmain.swiftAt this point we didn’t implement any methods



So what’s going on here? We turned to our old friendsSILTake 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_JSONDecoderThe implementation of the:







Let’s compare:





This is what we found by calling the current one againcontainerMethod implementation; And where is this method implemented? Is that where_JSONDecoderInside, let’s look at the next break point







And then the question is, how do we iterate through our currentKeyValuekey-valueAssignment operation of? First of all, let’s seeSILfile



The enumeration type is first created in memoryname, and then callDecode ()Method. At this point we’re going back to our



findKeyedDecodingContainerConcrete implementation of:



At this timeContainerWhat is? Is that what it is





Here,DecodeMethods are_boxLet’s go back and look for it again



So basically at this point we see that we’re actually calling_JSONKeyedDecodingContainerthedecodeMethod, 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 和 nameBut wesubjectNameIt doesn’t code properly. Here we godebugLook at the current why?





Our currenttype 是 LGTeacherDid not complyifSo the current code executes to the following branch:





That hereencodeMethods are followed by usCodableAfter the protocol, the system automatically helps us implement, we passSILTake a look:





Let’s see againLGPersonThe default implementation, so naturally we don’t have access to the current oneLGPersonThe code will only be correctage , nameIt’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 thisdecoderThen 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 currentLGPersonIt’s an agreement. He can’t keep it



So what do we do? At this point we’re probably thinking about the directLGTeacher , LGPartTimeTeacherIn the implementationDecode and encodeThen 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 isLGPersonBoxWhat 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