Introduction of Codable

Codable is a new feature introduced in Swift4.0 to replace the NSCoding protocol.

  • Codable serializes in-app data structures into exchangeable data and deserializes common data formats into structures for internal use, making it easier to convert objects and their representations to and from each other.

  • The Codable protocol has perfect support for basic Swift built-in types. It supports structures, enumerations, and classes to convert weakly-typed JSON data into strongly-typed data for use in code and, thanks to the compiler, save developers a lot of code duplication.

  • Swift basic embedded types follow the Codable protocol by default, like String, Int, Double, Date, and Data. Array, Dictionary, and Optional all follow Codable protocols for direct coding and decoding.

  • Cocodable is a hybrid type that is a combination of Encodable and Decodable protocols. If you are Codable, you are responsible for Encodable and Decodable. To encode and decode custom types or data models, you must follow the Codable protocol.

/// A type that can convert itself into and out of an external representation. /// `Codable` is a type alias for the `Encodable` and `Decodable` protocols. /// When you use `Codable` as a type or a generic constraint, it matches /// any type that conforms to both protocols. public typealias Codable = Decodable & Encodable /// A type that can decode itself from an external representation. public protocol Decodable { /// Creates a new instance by decoding from the given decoder. /// /// This initializer throws an error if reading from the decoder fails, or /// if the data read is corrupted or otherwise invalid. /// /// - Parameter decoder: The decoder to read data from. init(from decoder: Decoder) throws } /// A type that can encode itself to an external representation. public protocol Encodable { /// Encodes this value into the given encoder. /// /// If the value fails to encode anything, `encoder` will encode an empty /// keyed container in its place. /// /// This function throws an error if any values are  invalid for the given /// encoder's format. /// /// - Parameter encoder: The encoder to write data to. func encode(to encoder: Encoder) throws }Copy the code

Basic usage

Look at the code above:

Decodable

The Decodable protocol defines an initialization function:

init(from decoder: Decoder) throws
Copy the code

Decodable protocol compliant types can be initialized with any Decoder object to complete a decoding process. Such as:

struct CPerson: Decodable{ var name: String var age: Int } let jsonString = """ { "age": 18, "name": } """ let jsonData = jsonString.data(using: .utf8) if let data = jsonData{ let jsonDecoder = JSONDecoder() let t = try? Jsondecoder. decode(cperson. self, from: data) print(t??Copy the code

JSONDecoder is a parser that can resolve any type that implements the Decodable protocol.

Encodable

The Encodable protocol defines a method that:

func encode(to encoder: Encoder) throws
Copy the code

Any Encoder object that is created in compliance with the Encodable protocol type completes the coding process. The Swift standard library supports Codable protocols for types like String, Int, Double, and Data, Date, and URL by default.

Let t = CPerson(name: "liu ", age: 18) let jsonEncoder = jsonEncoder () let jsonData = try? jsonEncoder.encode(t) if let data = jsonData { let jsonString = String(decoding: data, as: UTF8.self) print(jsonString) }Copy the code

JSONEncoder is an encoder that can parse any type that implements the Encodable protocol.

Nested models

For nested models, all models must follow the Codable protocol before they can be encoded and decoded. Compile an error if a type does not conform to Codable protocol.

The JSON data contains Optional values

In daily development, null is unavoidable when connecting data with the server. If handled improperly, it may be possible to use crash-compatible solutions: Declare the attributes that may be NULL as Optional values

Contains an array

Make sure items in an array are Codable for coding and decoding.

The array collection

When parsing a collection of arrays, pass [

]. Self to decode. For example:

let t = try? jsonDecoder.decode([CPerson].self, from: data)
Copy the code

inheritance

Members of a subclass cannot be resolved and coded into Codable for a parent class that is cidcidar. The reason is simply that subclasses do not implement protocol functions.

class CPerson: Codable { var name: String? } class SuperMan: CPerson { var color: String? } let jsonString = """ { "name": "Zang", "color": "Red" } """ let jsonData = jsonString.data(using: .utf8) if let data = jsonData{ let jsonDecoder = JSONDecoder() let c = try? jsonDecoder.decode(SuperMan.self, from: Data) print("name: \(c?.name), partTime: \(c?.color)")} /// Result name: Optional("Zang"), color: nilCopy the code

In the code above, the parent classes follow the Codable protocol. Only attributes of the parent class can be resolved to nil

Same logic: Subclasses follow the Codable protocol. Only subclasses can be resolved to nil.

A tuple type

For example, a point, location: [20, 10] requires an init(from decoder: decoder) method when interpreting it in Codable

struct Point: Codable { var x: Double var y: Int init(from decoder: Decoder) throws{ var contaioner = try decoder.unkeyedContainer() self.x = try contaioner.decode(Double.self) self.y = try contaioner.decode(Int.self) } } struct RawSeverResponse: Codable{ var location: Point } let jsonString = """ { "location": [20, 10] } """ let jsonData = jsonString.data(using: .utf8) if let data = jsonData{ let jsonDecoder = JSONDecoder() let t = try? jsonDecoder.decode(RawSeverResponse.self, From: data) print(t??Copy the code

In the preceding code, unkeyedContainer is used to resolve the current Key, that is, x and y. Then assign the x and y attributes unilaterally to resolve the tuple, not using a direct correspondence to the Swift tuple, only through the structure

agreement

When a structure complies with custom protocols, it is Codable for coding and decoding. Or make custom protocols follow Codable for successful codecs.

protocol CProtocol { var name: String{ get set } } protocol BProtocol: Codable { var name: String{ get set } } struct CPersion: CProtocol, Codable { var name: String var partTime: Int? } struct BPersion: BProtocol { var name: String var partTime: Int? } let jsonString = "{" {"name":" linzhilin ", "partTime": 20} "" let jsonData = jsonString.data(using: .utf8) if let data = jsonData{ let jsonDecoder = JSONDecoder() let t = try jsonDecoder.decode(CPersion.self, from: data) let t1 = try jsonDecoder.decode(BPersion.self, from: data) print(t) print(t1) } //result CPersion(name: "Lin ", partTime: Optional(20)) BPersion(name:" Lin ", partTime: Optional(20))Copy the code

There are structural differences between data and objects

When there is a structural difference between the data returned by the server and the client object, you can handle it as follows:

struct CPerson: 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": "Ao", "item. 0" : "pressure", "item. 2" : "on", "item. 1" : "eagle"} ", "" let jsonData = jsonString) data (using: .utf8) if let data = jsonData { let jsonDecoder = JSONDecoder() let t = try jsonDecoder.decode(CPerson.self, from: Data) print (t)} / / CPerson (elements: [" pressure ", "eagle", "on" and "ao"])Copy the code

The above code, init(from decoder: decoder) method custom parsing, let CodingKeys follow the CaseIterable protocol, make the enumeration type with traversable characteristics. Only the decoding function is required, and only the Decodable protocol can be followed

The source code parsing

What is the code for cods? What is the code for cods? What is the code for cods

public typealias Codable = Decodable & Encodable
Copy the code

Decodable

A custom type can be parsed by executing init(from decoder: decoder) throws decoder.

public protocol Decodable {
    init(from decoder: Decoder) throws
}
Copy the code

Decoder

The Decoder in the init method of Decodable is also a protocol that provides a protocol for decoding data types as follows:

public protocol Decoder {
    var codingPath: [CodingKey] { get }
    var userInfo: [CodingUserInfoKey : Any] { get }
    func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey
    func unkeyedContainer() throws -> UnkeyedDecodingContainer
    func singleValueContainer() throws -> SingleValueDecodingContainer
}
Copy the code

JSONDecoder

Decoder provides a Decoder JSONDecoder, defined as follows:

open class JSONDecoder { public enum DateDecodingStrategy { case deferredToDate case secondsSince1970 case MillisecondsSince1970 @ the available (macOS 10.12, iOS 10.0, watchOS 3.0, 10.0, tvOS *) case iso8601 case formatted(DateFormatter) case custom((_ decoder: Decoder) throws -> Date) } public enum DataDecodingStrategy { case deferredToData case base64 case custom((_ decoder: Decoder) throws -> Data) } public enum NonConformingFloatDecodingStrategy { case `throw` case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String) } public enum KeyDecodingStrategy { case useDefaultKeys case convertFromSnakeCase case custom((_ codingPath: [CodingKey]) -> CodingKey) fileprivate static func _convertFromSnakeCase(_ stringKey: String) -> String { guard ! stringKey.isEmpty else { return stringKey } guard let firstNonUnderscore = stringKey.firstIndex(where: { $0 ! = "_" }) else { return stringKey } var lastNonUnderscore = stringKey.index(before: stringKey.endIndex) while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" { stringKey.formIndex(before: &lastNonUnderscore) } let keyRange = firstNonUnderscore... lastNonUnderscore let leadingUnderscoreRange = stringKey.startIndex.. <firstNonUnderscore let trailingUnderscoreRange = stringKey.index(after: lastNonUnderscore).. <stringKey.endIndex let components = stringKey[keyRange].split(separator: "_") let joinedString : String if components.count == 1 { joinedString = String(stringKey[keyRange]) } else { joinedString = ([components[0].lowercased()] + components[1...] .map { $0.capitalized }).joined() } let result : String if (leadingUnderscoreRange.isEmpty && trailingUnderscoreRange.isEmpty) { result = joinedString } else if (! leadingUnderscoreRange.isEmpty && ! trailingUnderscoreRange.isEmpty) { result = String(stringKey[leadingUnderscoreRange]) + joinedString + String(stringKey[trailingUnderscoreRange]) } else if (! leadingUnderscoreRange.isEmpty) { result = String(stringKey[leadingUnderscoreRange]) + joinedString } else { result = joinedString + String(stringKey[trailingUnderscoreRange]) } return result } } open var dateDecodingStrategy: DateDecodingStrategy = .deferredToDate open var dataDecodingStrategy: DataDecodingStrategy = .base64 open var nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy = .throw open var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys open var userInfo: [CodingUserInfoKey : Any] = [:] fileprivate struct _Options { let dateDecodingStrategy: DateDecodingStrategy let dataDecodingStrategy: DataDecodingStrategy let nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy let keyDecodingStrategy: KeyDecodingStrategy let userInfo: [CodingUserInfoKey : Any] } fileprivate var options: _Options { return _Options(dateDecodingStrategy: dateDecodingStrategy, dataDecodingStrategy: dataDecodingStrategy, nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy, keyDecodingStrategy: keyDecodingStrategy, userInfo: userInfo) } public init() {} open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T { let topLevel: Any do { topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments) } catch { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error)) } let decoder = _JSONDecoder(referencing: topLevel, options: self.options) guard let value = try decoder.unbox(topLevel, as: type) else { throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value.")) } return value } }Copy the code

JSONDecoder is an implementation of Decoder, which contains various Settings and operations. Let’s look at them one by one

DateDecodingStrategy

The JSONDecoder class defines the DateDecodingStrategy enumeration type to return the date format of which strategy, as shown in the following example:

Let jsonDecoder = jsonDecoder () //deferredToDate: The default policy jsonDecoder. DateDecodingStrategy =. DeferredToDate print (" deferredToDate = = = = = > \ (a try jsonDecoder.decode(CPerson.self, from: Data)) ") / / secondsSince1970: The number of seconds a distance 1970.01.01 jsonDecoder. DateDecodingStrategy =. SecondsSince1970 print (" secondsSince1970 = = = = = > \ (a try jsonDecoder.decode(CPerson.self, from: Data)) ") / / distance 1970.01.01 milliseconds jsonDecoder. DateDecodingStrategy =. MillisecondsSince1970 print("millisecondsSince1970=====>\(try jsonDecoder.decode(CPerson.self, from: / / decoding data)) ") to the ISO - 8601 format (RFC 3339 format) jsonDecoder dateDecodingStrategy =. Iso8601 print (" iso8601 = = = = = > \ (a try jsonDecoder.decode(CPerson.self, from: Data))") // Background custom format, Let formatter = DateFormatter() formatter. DateFormat = "YYYY yyyy MM MM DD HH: MM :ss" jsonDecoder.dateDecodingStrategy = .formatted(formatter) print("formatted=====>\(try jsonDecoder.decode(CPerson.self, from: Data))") // Custom format, Through closure expression returns the Date type jsonDecoder. DateDecodingStrategy =. The custom () {decoder - > the Date in the let the container = a try decoder.singleValueContainer() let strDate = try container.decode(String.self) let formatter = DateFormatter() DateFormat = "yyyY-MM-DD HH: MM :ss" guard let date = formatter.date(from: strDate) else { return Date() } return date } print("custom=====>\(try jsonDecoder.decode(CPerson.self, from: data))")Copy the code

Set the corresponding time format according to the data type of the server

DataDecodingStrategy

DataDecodingStrategy: binary decoding strategy

  • deferredToData: Indicates the default decoding policy
  • base64Use:base64decoding
  • custom: Custom decoding mode

NonConformingFloatDecodingStrategy

NonConformingFloatDecodingStrategy: illegal floating-point encoding strategy

  • throw
  • convertFromString

KeyDecodingStrategy

KeyDecodingStrategy: Key encoding strategy

  • useDefaultKeys
  • convertFromSnakeCase
  • custom

decodemethods

The decode method is used to convert JSON to the specified type, receiving t. type and Data Data

open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
    let topLevel: Any
    do {
        topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
    } catch {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
    }

    let decoder = _JSONDecoder(referencing: topLevel, options: self.options)

    guard let value = try decoder.unbox(topLevel, as: type) else {
        throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
    }

    return value
}
Copy the code
  1. The generic type of the input parameterTThat must be followedDecodableagreement
  2. useJSONSerializationwilldataData serialized to a dictionaryKeyValue
  3. Calling inner classes_JSONDecoderIncoming dictionary and encoding policy returndecoderobject
  4. throughdecoderThe object’sunboxMethod decodes and returnsvalue

Let’s look at the inner class _JSONDecoder which is important here

_JSONDecoder

_JSONDecoder is an internal class used to decode operations, which follow the Decoder protocol code:

fileprivate class _JSONDecoder : Decoder { fileprivate var storage: _JSONDecodingStorage fileprivate let options: JSONDecoder._Options fileprivate(set) public var codingPath: [CodingKey] public var userInfo: [CodingUserInfoKey : Any] { return self.options.userInfo } fileprivate init(referencing container: Any, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) { self.storage = _JSONDecodingStorage() self.storage.push(container: container) self.codingPath = codingPath self.options = options } public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> { guard ! (self.storage.topContainer is NSNull) else { throw DecodingError.valueNotFound(KeyedDecodingContainer<Key>.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot get keyed decoding container -- found null value instead.")) } guard let topContainer = self.storage.topContainer as? [String : Any] else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: self.storage.topContainer) } let container = _JSONKeyedDecodingContainer<Key>(referencing: self, wrapping: topContainer) return KeyedDecodingContainer(container) } public func unkeyedContainer() throws -> UnkeyedDecodingContainer { guard ! (self.storage.topContainer is NSNull) else { throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot get unkeyed decoding container -- found null value instead.")) } guard let topContainer = self.storage.topContainer as? [Any] else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: self.storage.topContainer) } return _JSONUnkeyedDecodingContainer(referencing: self, wrapping: topContainer) } public func singleValueContainer() throws -> SingleValueDecodingContainer { return self } }Copy the code

init

Let’s start with the constructor method called by the decode method: init, which takes three parameters

  • container: serializedKeyValue
  • codingPath:CodingKeyAn empty array of type
  • options: Encoding policy
fileprivate init(referencing container: Any, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) {
    self.storage = _JSONDecodingStorage()
    self.storage.push(container: container)
    self.codingPath = codingPath
    self.options = options
}
Copy the code

Its main job is

  • Creating an inner class_JSONDecodingStorage
  • usepushMethod stores the data container to be decoded
  • Initialize options and codingPath (empty array)

_JSONDecodingStorage

_JSONDecodingStorage is a structure that contains an array of Any types and provides methods such as push and popContainer. It is a stack container that manages containers that we pass in. Parameter Description Container

(keyedBy type: key. type) throws -> KeyedDecodingContainer

So what did Unbox do

unbox

The unbox method is used for decoding operations, matching the corresponding type and then performing the conditional branch

fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
    return try unbox_(value, as: type) as? T
}

fileprivate func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
    #if DEPLOYMENT_RUNTIME_SWIFT

    if type == Date.self {
        guard let date = try self.unbox(value, as: Date.self) else { return nil }
        return date
    } else if type == Data.self {
        guard let data = try self.unbox(value, as: Data.self) else { return nil }
        return data
    } else if type == URL.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."))
        }
        return url
    } else if type == Decimal.self {
        guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil }
        return decimal
    } else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker.Type {
        return try self.unbox(value, as: stringKeyedDictType)
    } else {
        self.storage.push(container: value)
        defer { self.storage.popContainer() }
        return try type.init(from: self)
    }
    #else
    if type == Date.self || type == NSDate.self {
        return try self.unbox(value, as: Date.self)
    } else if type == Data.self || type == NSData.self {
        return try self.unbox(value, as: Data.self)
    } else if type == URL.self || type == 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."))
        }
        
        return url
    } else if type == Decimal.self || type == NSDecimalNumber.self {
        return try self.unbox(value, as: Decimal.self)
    } else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker.Type {
        return try self.unbox(value, as: stringKeyedDictType)
    } else {
        self.storage.push(container: value)
        defer { self.storage.popContainer() }
        return try type.init(from: self)
    }
    #endif
}
Copy the code

There are different branches of the Unbox that will do something to T. Unbox method is a branch of code, in view of the decode _JSONStringDictionaryDecodableMarker type,

else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker.Type {
    return try self.unbox(value, as: stringKeyedDictType)
}
Copy the code

See the definition of _JSONStringDictionaryDecodableMarker

fileprivate protocol _JSONStringDictionaryEncodableMarker { }

extension Dictionary : _JSONStringDictionaryEncodableMarker where Key == String, Value: Encodable { }

fileprivate protocol _JSONStringDictionaryDecodableMarker {
    static var elementType: Decodable.Type { get }
}

extension Dictionary : _JSONStringDictionaryDecodableMarker where Key == String, Value: Decodable {
    static var elementType: Decodable.Type { return Value.self }
}
Copy the code

Check for _JSONStringDictionaryDecodableMarker decoding method

fileprivate func unbox<T>(_ value: Any, as type: _JSONStringDictionaryDecodableMarker.Type) throws -> T? { guard ! (value is NSNull) else { return nil } var result = [String : Any]() guard let dict = value as? NSDictionary else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) } let elementType = type.elementType for (key, value) in dict { let key = key as! String self.codingPath.append(_JSONKey(stringValue: key, intValue: nil)) defer { self.codingPath.removeLast() } result[key] = try unbox_(value, as: elementType) } return result as? T }Copy the code

For _JSONStringDictionaryDecodableMarker types of decoding process, is actually a recursive operations, this is for dic decoding Other similar

We have here the last section of the main branch of study

 else {
        self.storage.push(container: value)
        defer { self.storage.popContainer() }
        return try type.init(from: self)
    }
Copy the code

Init (from:), self, _JSONDecoder type is the type of value that we parse, init(from:) should be our Decodable init method

public protocol Decodable {
    init(from decoder: Decoder) throws
}
Copy the code

So here’s the question. Throws throws init(from decoder: decoder) throws throws init(from decoder: decoder) throws throws init(from decoder: decoder) Using the command swiftc – emit – sil main. Swift | xcrun swift – demangle we see only inherited Decodable data structure, what’s it like in the sil.

struct CPerson: Decodable{
    var name: String
    var age: Int
}

let jsonString = """
{
    "age": 18,
    "name": "Zang",
}
"""

let jsonData = jsonString.data(using: .utf8)
let jsonDecoder = JSONDecoder()
let t = try? jsonDecoder.decode(CPerson.self, from: jsonData!)
print("-----end")
Copy the code

Above is a simple Decoder decoding

import Foundation struct CPerson : Decodable { @_hasStorage var name: String { get set } @_hasStorage var age: Int { get set } enum CodingKeys : CodingKey { case name case age @_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: CPerson.CodingKeys, _ b: CPerson.CodingKeys) -> Bool func hash(into hasher: inout Hasher) init? (stringValue: String) init? (intValue: Int) var hashValue: Int { get } var intValue: Int? { get } var stringValue: String { get } } init(from decoder: Decoder) throws init(name: String, age: Int) }Copy the code
  • Compiler automatic implementationCodingKeysEnumeration type, and followCodingKeyThe agreement. It goes through the decoding processCodingKeysTo find the correspondingcase
  • Compiler automatic implementationdecodeDecoding method:init(from decoder: Decoder)

This is one of the most convenient areas for Codable, where Apple helped us implement a basic type of resolution.

Let’s look at cperson.init (from:)

/ CPerson.init(from:) sil hidden @main.CPerson.init(from: Swift.Decoder) throws -> main.CPerson : $@convention(method) (@in Decoder, @thin CPerson.Type) -> (@owned CPerson, @error Error) { // %0 "decoder" // users: %69, %49, %9, %6 // %1 "$metatype" bb0(%0 : $*Decoder, %1 : $@thin CPerson.Type): %2 = alloc_stack $Builtin.Int2 // users: %70, %27, %5, %78, %52 %3 = alloc_stack [dynamic_lifetime] $CPerson, var, name "self" // users: %40, %24, %50, %73, %77, %51 %4 = integer_literal $Builtin.Int2, 0 // user: %5 store %4 to %2 : $*Builtin.Int2 // id: %5 debug_value_addr %0 : $*Decoder, let, name "decoder", argno 1 // id: %6 debug_value undef : $Error, var, name "$error", argno 2 // id: %7 %8 = alloc_stack $KeyedDecodingContainer<CPerson.CodingKeys>, let, name "container" // users: %45, %44, %37, %66, %65, %21, %60, %59, %13, %55 %9 = open_existential_addr immutable_access %0 : $*Decoder to $*@opened("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") Decoder // users: %13, %13, %12 %10 = metatype $@thin CPerson.CodingKeys.Type %11 = metatype $@thick CPerson.CodingKeys.Type // user: %13 %12 = witness_method $@opened("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %9 : $*@opened(" f185EE86-7F23-11EC-ABA0-A0999b1CB1FF ") Decoder: $@convention(witness_method: Decoder) <τ_0_0 where τ_0_0: Decoder > < tau _1_0 where tau _1_0: CodingKey> (@thick τ _1_0.type, @in_guaranteed τ_0_0) -> (@out KeyedDecodingContainer<τ_1_0>, @error Error) // type-defs: %9; user: %13 try_apply %12<@opened("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") Decoder, CPerson.CodingKeys>(%8, %11, %9) : $@convention(witness_method: Decoder) <τ_0_0 where τ_0_0: Decoder><τ_1_0 where τ_1_0: CodingKey> (@thick τ _1_0.type, @in_guaranteed τ_0_0) -> (@out KeyedDecodingContainer<τ_1_0>, @error error), normal BB1, error bb4 // type-defs: %9; id: %13Copy the code

Look only at the last

  • create$KeyedDecodingContainerTemporary constant ofcontainer
  • inPWTWe found it in the protocol witness listcontainerMethod and call

Is it to do the following work?

    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)
    }
Copy the code

This is familiar.

KeyedDecodingContainer

So let’s take a look hereContainer, and container. Decode, decoder. ContainerLook at the Decodable protocol,DecoderExists in the protocolcontainerMethod declarationView the source code_JSONDecoderthecontainerMethod, returnKeyedDecodingContainer<Key>

public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> { guard ! (self.storage.topContainer is NSNull) else { throw DecodingError.valueNotFound(KeyedDecodingContainer<Key>.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot get keyed decoding container -- found null value instead.")) } guard let topContainer = self.storage.topContainer as? [String : Any] else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: self.storage.topContainer) } let container = _JSONKeyedDecodingContainer<Key>(referencing: self, wrapping: topContainer) return KeyedDecodingContainer(container) }Copy the code

KeyedDecodingContainer < K > is a structure, follow KeyedDecodingContainerProtocol agreement. There is a condition that K must comply with the CodingKey protocol. The structure defines various types of decoding methods, and the corresponding decode method will be matched according to different types

public struct KeyedDecodingContainer<K> : KeyedDecodingContainerProtocol where K : CodingKey {
    public typealias Key = K
    public init<Container>(_ container: Container) where K == Container.Key, Container : KeyedDecodingContainerProtocol
    public var codingPath: [CodingKey] { get }
    public var allKeys: [KeyedDecodingContainer<K>.Key] { get }
    public func contains(_ key: KeyedDecodingContainer<K>.Key) -> Bool
    public func decodeNil(forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool
    public func decode(_ type: Bool.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool
    public func decode(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String
    public func decode(_ type: Double.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Double
    public func decode(_ type: Float.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Float
    public func decode(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int
    public func decode(_ type: Int8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int8
    public func decode(_ type: Int16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int16
    public func decode(_ type: Int32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int32
    public func decode(_ type: Int64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int64
    public func decode(_ type: UInt.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt
    public func decode(_ type: UInt8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt8
    public func decode(_ type: UInt16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt16
    public func decode(_ type: UInt32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt32
    public func decode(_ type: UInt64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt64
    public func decode<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T where T : Decodable
    public func decodeIfPresent(_ type: Bool.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool?
    public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String?
    public func decodeIfPresent(_ type: Double.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Double?
    public func decodeIfPresent(_ type: Float.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Float?
    public func decodeIfPresent(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int?
    public func decodeIfPresent(_ type: Int8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int8?
    public func decodeIfPresent(_ type: Int16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int16?
    public func decodeIfPresent(_ type: Int32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int32?
    public func decodeIfPresent(_ type: Int64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int64?
    public func decodeIfPresent(_ type: UInt.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt?
    public func decodeIfPresent(_ type: UInt8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt8?
    public func decodeIfPresent(_ type: UInt16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt16?
    public func decodeIfPresent(_ type: UInt32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt32?
    public func decodeIfPresent(_ type: UInt64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt64?
    public func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T? where T : Decodable
    public func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey
    public func nestedUnkeyedContainer(forKey key: KeyedDecodingContainer<K>.Key) throws -> UnkeyedDecodingContainer
    public func superDecoder() throws -> Decoder
    public func superDecoder(forKey key: KeyedDecodingContainer<K>.Key) throws -> Decoder
    public func decodeIfPresent(_ type: Bool.Type, forKey key: K) throws -> Bool?
    public func decodeIfPresent(_ type: String.Type, forKey key: K) throws -> String?
    public func decodeIfPresent(_ type: Double.Type, forKey key: K) throws -> Double?
    public func decodeIfPresent(_ type: Float.Type, forKey key: K) throws -> Float?
    public func decodeIfPresent(_ type: Int.Type, forKey key: K) throws -> Int?
    public func decodeIfPresent(_ type: Int8.Type, forKey key: K) throws -> Int8?
    public func decodeIfPresent(_ type: Int16.Type, forKey key: K) throws -> Int16?
    public func decodeIfPresent(_ type: Int32.Type, forKey key: K) throws -> Int32?
    public func decodeIfPresent(_ type: Int64.Type, forKey key: K) throws -> Int64?
    public func decodeIfPresent(_ type: UInt.Type, forKey key: K) throws -> UInt?
    public func decodeIfPresent(_ type: UInt8.Type, forKey key: K) throws -> UInt8?
    public func decodeIfPresent(_ type: UInt16.Type, forKey key: K) throws -> UInt16?
    public func decodeIfPresent(_ type: UInt32.Type, forKey key: K) throws -> UInt32?
    public func decodeIfPresent(_ type: UInt64.Type, forKey key: K) throws -> UInt64?
    public func decodeIfPresent<T>(_ type: T.Type, forKey key: K) throws -> T? where T : Decodable
}
Copy the code

The code structure above defines many types of decode methods generated by Apple’s in-house tools that generate Codable. Swift source files from the Codable. Swift.gyb template file.

Codable.swift. Gyb template file:

Define a collection that holds all of the embedded types that can be encoded or decoded. In order to%Start and end, regarded as the beginning and end of the code, passpythonControl, equivalent to a template fileNot to%Start and end, as direct text output. I won’t go into detail here.

When we implement the init(from decoder: decoder) method ourselves, where decode and CodingKeys are automatically generated by the system, we can redefine CodingKeys ourselves to deal with structural differences between data and objects. You can also re-implement the init(from decoder: decoder) method.

Decodable summary

Here’s a picture, which I think is pretty clear to summarize

Encodable

Encodable: code for converting custom types into weak-typed data

public protocol Encodable { /// Encodes this value into the given encoder. /// /// If the value fails to encode anything, `encoder` will encode an empty /// keyed container in its place. /// /// This function throws an error if any values are  invalid for the given /// encoder's format. /// /// - Parameter encoder: The encoder to write data to. func encode(to encoder: Encoder) throws }Copy the code

Encoder

Func Encode (to Encoder: Encoder) throws Encoder (to Encoder: Encoder).

public protocol Encoder {

    /// The path of coding keys taken to get to this point in encoding.
    var codingPath: [CodingKey] { get }

    /// Any contextual information set by the user for encoding.
    var userInfo: [CodingUserInfoKey : Any] { get }

    /// Returns an encoding container appropriate for holding multiple values
    /// keyed by the given key type.
    ///
    /// You must use only one kind of top-level encoding container. This method
    /// must not be called after a call to `unkeyedContainer()` or after
    /// encoding a value through a call to `singleValueContainer()`
    ///
    /// - parameter type: The key type to use for the container.
    /// - returns: A new keyed encoding container.
    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey

    /// Returns an encoding container appropriate for holding multiple unkeyed
    /// values.
    ///
    /// You must use only one kind of top-level encoding container. This method
    /// must not be called after a call to `container(keyedBy:)` or after
    /// encoding a value through a call to `singleValueContainer()`
    ///
    /// - returns: A new empty unkeyed container.
    func unkeyedContainer() -> UnkeyedEncodingContainer

    /// Returns an encoding container appropriate for holding a single primitive
    /// value.
    ///
    /// You must use only one kind of top-level encoding container. This method
    /// must not be called after a call to `unkeyedContainer()` or
    /// `container(keyedBy:)`, or after encoding a value through a call to
    /// `singleValueContainer()`
    ///
    /// - returns: A new empty single value container.
    func singleValueContainer() -> SingleValueEncodingContainer
}
Copy the code

Look at the 🌰

struct CPerson: Encodable { var name: String var age: Int } let value = CPerson(name: "liu", age: 99) let jsonEncoder = JSONEncoder() let data = try? jsonEncoder.encode(value) let str = String(data: data! , encoding: .utf8) print(str)Copy the code

The encode method, which accepts the generic T, which must follow the Encodable protocol and returns Data

open func encode<T : Encodable>(_ value: T) throws -> Data {
    let encoder = _JSONEncoder(options: self.options)

    guard let topLevel = try encoder.box_(value) else {
        throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level (T.self) did not encode any values."))
    }

    let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue).union(.fragmentsAllowed)

    do {
        return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
    } catch {
        throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error))
    }
}
Copy the code

This process is the opposite of Decoder

  • Creating an inner class_JSONEncoder
  • callbox_Method wrapped as a dictionary type
  • useJSONSerializationSerialized asDatadata

So let’s look at _JSONEncoder

_JSONEncoder

The _JSONEncoder class follows the Encoder protocol and mainly provides the container encoding method, returning KeyedEncodingContainer

fileprivate class _JSONEncoder : Encoder { fileprivate var storage: _JSONEncodingStorage fileprivate let options: JSONEncoder._Options public var codingPath: [CodingKey] public var userInfo: [CodingUserInfoKey : Any] { return self.options.userInfo } fileprivate init(options: JSONEncoder._Options, codingPath: [CodingKey] = []) { self.options = options self.storage = _JSONEncodingStorage() self.codingPath = codingPath } fileprivate var canEncodeNewValue: Bool { return self.storage.count == self.codingPath.count } public func container<Key>(keyedBy: Key.Type) -> KeyedEncodingContainer<Key> { let topContainer: NSMutableDictionary if self.canEncodeNewValue { topContainer = self.storage.pushKeyedContainer() } else { guard let container = self.storage.containers.last as? NSMutableDictionary else { preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") } topContainer = container } let container = _JSONKeyedEncodingContainer<Key>(referencing: self, codingPath: self.codingPath, wrapping: topContainer) return KeyedEncodingContainer(container) } public func unkeyedContainer() -> UnkeyedEncodingContainer { let topContainer: NSMutableArray if self.canEncodeNewValue { topContainer = self.storage.pushUnkeyedContainer() } else { guard let container = self.storage.containers.last as? NSMutableArray else { preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") } topContainer = container } return _JSONUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) } public func singleValueContainer() -> SingleValueEncodingContainer { return self } fileprivate func box_(_ value: Encodable) throws -> NSObject? {... Do {// call the Encodable method try value.encode(to: self)}...... }}Copy the code

box_

The box_ method, depending on the value type, calls different branches of code to wrap the value into the corresponding data type. The following code, if value is not a data type defined above, such as CPerson, will eventually call value.encode(to: self), passing in self as _JSONEncoder

return try box(value as! [String : Encodable])

fileprivate func box_(_ value: Encodable) throws -> NSObject? { let type = Swift.type(of: value) #if DEPLOYMENT_RUNTIME_SWIFT if type == Date.self { // Respect Date encoding strategy return try self.box((value as! Date)) } else if type == Data.self { // Respect Data encoding strategy return try self.box((value as! Data)) } else if type == URL.self { // Encode URLs as single strings. return self.box((value as! URL).absoluteString) } else if type == Decimal.self { // JSONSerialization can consume NSDecimalNumber values. return NSDecimalNumber(decimal: value as! Decimal) } else if value is _JSONStringDictionaryEncodableMarker { return try box(value as! [String : Encodable]) } #else if type == Date.self || type == NSDate.self { // Respect Date encoding strategy return try self.box((value as! Date)) } else if type == Data.self || type == NSData.self { // Respect Data encoding strategy return try self.box((value  as! Data)) } else if type == URL.self || type == NSURL.self { // Encode URLs as single strings. return self.box((value as! URL).absoluteString) } else if type == Decimal.self { // JSONSerialization can consume NSDecimalNumber values. return NSDecimalNumber(decimal: value as! Decimal) } else if value is _JSONStringDictionaryEncodableMarker { return try box(value as! [String : Encodable]) } #endif // The value should request a container from the _JSONEncoder. let depth = self.storage.count do { try value.encode(to: self) } catch { // If the value pushed a container before throwing, pop it back off to restore state. if self.storage.count > depth { let _ = self.storage.popContainer() } throw error } //  The top container should be a new container. guard self.storage.count > depth else { return nil } return self.storage.popContainer() }Copy the code

The container method provides KeyedEncodingContainer

KeyedEncodingContainer

KeyedEncodingContainer < K > structure, following KeyedEncodingContainerProtocol agreement, K must follow CodingKey agreement, defines the internal various types corresponding to encode method

public struct KeyedEncodingContainer<K> : KeyedEncodingContainerProtocol where K : CodingKey {
    public typealias Key = K
    public init<Container>(_ container: Container) where K == Container.Key, Container : KeyedEncodingContainerProtocol
    public var codingPath: [CodingKey] { get }
    public mutating func encodeNil(forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: Bool, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: String, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: Double, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: Float, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: Int, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: Int8, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: Int16, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: Int32, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: Int64, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: UInt, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: UInt8, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: UInt16, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: UInt32, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode(_ value: UInt64, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encode<T>(_ value: T, forKey key: KeyedEncodingContainer<K>.Key) throws where T : Encodable
    public mutating func encodeConditional<T>(_ object: T, forKey key: KeyedEncodingContainer<K>.Key) throws where T : AnyObject, T : Encodable
    public mutating func encodeIfPresent(_ value: Bool?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: String?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: Double?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: Float?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: Int?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: Int8?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: Int16?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: Int32?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: Int64?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: UInt?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: UInt8?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: UInt16?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: UInt32?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent(_ value: UInt64?, forKey key: KeyedEncodingContainer<K>.Key) throws
    public mutating func encodeIfPresent<T>(_ value: T?, forKey key: KeyedEncodingContainer<K>.Key) throws where T : Encodable
    public mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: KeyedEncodingContainer<K>.Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey
    public mutating func nestedUnkeyedContainer(forKey key: KeyedEncodingContainer<K>.Key) -> UnkeyedEncodingContainer
    public mutating func superEncoder() -> Encoder
    public mutating func superEncoder(forKey key: KeyedEncodingContainer<K>.Key) -> Encoder
    public mutating func encodeConditional<T>(_ object: T, forKey key: K) throws where T : AnyObject, T : Encodable
    public mutating func encodeIfPresent(_ value: Bool?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: String?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: Double?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: Float?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: Int?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: Int8?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: Int16?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: Int32?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: Int64?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: UInt?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: UInt8?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: UInt16?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: UInt32?, forKey key: K) throws
    public mutating func encodeIfPresent(_ value: UInt64?, forKey key: K) throws
    public mutating func encodeIfPresent<T>(_ value: T?, forKey key: K) throws where T : Encodable
}
Copy the code

Encodable summary

The analysis here is rather preliminary, because the basic idea is the same as Decodable

Remember on pit

Codable is not available

This is most common in cidcidr, which is cidcidr, and cidCIDr, which is cidCIDr, which is cidR, which is cidR, which is cidR, which is cidR, which is cidR, which is cidR, which is CIDR, which is CIDR, which is CIDR, which is CIDR, which is CIDR, which is CIDR, which is CIDR, which is CIDR

  • The superclass followsCodableProtocol, so the system automatically generated for the parent classencode(to encoder: Encoder)methods
  • Subclasses inherit from the parent class, but are not overriddenencode(to encoder: Encoder)methods
  • So in the process of coding, you still find the parent classencodeMethod, and finally only the parent class attributes can be successfully encoded

It can be seen clearly through the SIL

class CPerson : Decodable & Encodable { @_hasStorage @_hasInitialValue var name: String? { get set } @_hasStorage @_hasInitialValue var age: Int? { get set } @objc deinit init() enum CodingKeys : CodingKey { case name case age @_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: CPerson.CodingKeys, _ b: CPerson.CodingKeys) -> Bool var hashValue: Int { get } func hash(into hasher: inout Hasher) var stringValue: String { get } init? (stringValue: String) var intValue: Int? { get } init? (intValue: Int) } required init(from decoder: Decoder) throws func encode(to encoder: Encoder) throws }Copy the code

For CPerson, the system automatically generates CodingKeys and encode(to Encoder: encoder) methods

Look at his subclass, CSuperMan

@_inheritsConvenienceInitializers class CSuperMan : CPerson {
  @_hasStorage @_hasInitialValue var subjectName: String? { get set }
  @objc deinit
  override init()
  required init(from decoder: Decoder) throws
}
Copy the code

This can be done by overriding the encode(to encoder: encoder) method of the subclass.

class CSuperMan: CPerson {
    var subjectName: String?
    
    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)
        try super.encode(to: encoder)
    }
    
     required init(from decoder: Decoder) throws {
        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

Try super.encode(to: encoder) try super.encode(to: encoder). Using container.superencoder () in the super.encode method also adds a super node to the encoded JSON data, which is not recommended

override func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(subjectName, forKey: .subjectName) try super.encode(to: Container. SuperEncoder ()} / / output the following results: / / {" subjectName ":" Swift ", "super" : {" name ":" Zang ", "age" : 10}}Copy the code

Codec problem in polymorphic mode

When a structure stores a custom protocol, even if the protocol followsCodableProtocol, still compiling error, hint: protocol type does not matchDecodableProtocol, only allowedstruct,enum,class Consider using an intermediate layer for type erasure

protocol BaseInfo { var name: String { get set } var age: Int { get set } } struct SuperPerson: BaseInfo { var name: String var age: Int } struct HeroPerson: BaseInfo { var name: String var age: Int } struct PersonBox: BaseInfo, Codable { var name: String var age: Int init(_ baseinfo:BaseInfo) throws { self.name = baseinfo.name self.age = baseinfo.age } } struct Group: Codable {var groupName: String var person:[PersonBox]} let person:[BaseInfo] = [SuperPerson(name: "spiderman ", age: 99), HeroPerson(name: "monkey ", age: 500)] let array = try person.map(personbox.init) let aGroup = Group(groupName: groupName) "Axiaozu", person: array) let jsonEncoder = JSONEncoder() jsonEncoder.outputFormatting = .prettyPrinted let jsonData = try jsonEncoder.encode(aGroup) if let jsonString = String(data: jsonData, encoding: .utf8) {print(" code: \(jsonString)") } print("\n--------------------\n") let jsonDecoder = JSONDecoder() let c = try? Jsondecoder. decode(group. self, from: jsonData) print(" decode: \(c)") ///result decode: {"groupName" : "Axiaozu", "person" : [{" name ":" spider-man ", "age" : 99}, {" name ":" the wu is empty ", "age" : 500}]} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- decoding: Optional (LGMirror. Group (groupName: "Axiaozu", the person: [LGMirror. PersonBox (name: Spider-man, age: 99), lgmirror.personBox (name: "wukong ", age: 500)])Copy the code

The above code, encoding and decoding will work, but the output is of type LGPersonBox after decoding. What if the original type information needs to be preserved?

Option one uses the unBox method to restore the type

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 { var type: LGPersonType { get } var age: String { get set } var name: String { get set } } struct LGTeacher: LGPerson { var type: LGPersonType = LGPersonType.teacher var age: String var name: String static func unBox(_ person: LGPerson) -> LGPerson { return LGTeacher(age: person.age, name: person.name) } } struct LGParTimeTeacher: LGPerson { var type: LGPersonType = LGPersonType.partTeacher var age: String var name: String static func unBox(_ person: LGPerson) -> LGPerson { return LGParTimeTeacher(age: person.age, name: person.name) } } struct LGPersonBox: LGPerson, Codable { var type: LGPersonType var age: String var name: String init(_ person: LGPerson) { self.type = person.type self.age = person.age self.name = person.name } static func unBox(_ person: LGPerson) -> LGPerson { if person.type.metdadata == LGTeacher.self { return LGTeacher.unBox(person) } return LGParTimeTeacher.unBox(person) } } 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(" code: \(jsonString)") } print("\n--------------------\n") let jsonDecoder = JSONDecoder() let c = try? jsonDecoder.decode(Company.self, from: JsonData) print(" decoding: \(c?.person.map{lgPersonbox.unbox ($0)})") "Logic", // "person" : [ // { // "type" : "teacher", // "age" : "20", // "name" : "Kody" // }, // { // "type" : "partTeacher", // "age" : "30", // "name" : "Hank" / / / / / /}} / / / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / / / decoding: Optional ([LGSwiftTest. LGTeacher (type: LGSwiftTest.LGPersonType.teacher, age: "20", name: "Kody"), LGSwiftTest.LGParTimeTeacher(type: LGSwiftTest.LGPersonType.partTeacher, age: "30", name: "Hank")])Copy the code

Scheme 2: Type information can be encoded in the encoding process

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(" code: (jsonString)") } print("\n--------------------\n") let jsonDecoder = JSONDecoder() let c = try? Jsondecoder. decode(company. self, from: jsonData) print(" decode: (c)") "Logic", // "person" : [ // { // "type" : "teacher", // "p" : { // "age" : 20, // "name" : "Kody" // } // }, // { // "type" : "partTeacher", // "p" : { // "age" : 30, // "name" : "Hank" / / / / / / / /}}]} / / / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / / / decoding: Optional (LGSwiftTest.Com pany (person: [LGSwiftTest.LGPersonBox(p: LGSwiftTest.LGTeacher(age: 20, name: "Kody")), LGSwiftTest.LGPersonBox(p: LGSwiftTest.LGParTimeTeacher(age: 30, name: "Hank"))], companyName: "Logic"))Copy the code

Contrast with other codec libraries

  • SwiftyJSON: The value is evaluated by subscript. It is easy for the array to be out of bounds
  • ObjectMapper: Manually provide the mapping for each object, which is a lot of code
  • HandyJSON: The use of memory assignment method for codec operation, principle andCodableAll roads lead to Rome
  • Codable: Encounter inheritance and polymorphic mode, need to manually implement the codec function

HandyJSON and Codable have an edge over other libraries