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 policybase64
Use:base64
decodingcustom
: Custom decoding mode
NonConformingFloatDecodingStrategy
NonConformingFloatDecodingStrategy: illegal floating-point encoding strategy
throw
convertFromString
KeyDecodingStrategy
KeyDecodingStrategy: Key encoding strategy
useDefaultKeys
convertFromSnakeCase
custom
decode
methods
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
- The generic type of the input parameter
T
That must be followedDecodable
agreement - use
JSONSerialization
willdata
Data serialized to a dictionaryKeyValue
- Calling inner classes
_JSONDecoder
Incoming dictionary and encoding policy returndecoder
object - through
decoder
The object’sunbox
Method 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
:CodingKey
An empty array of typeoptions
: 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
- use
push
Method 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 implementation
CodingKeys
Enumeration type, and followCodingKey
The agreement. It goes through the decoding processCodingKeys
To find the correspondingcase
- Compiler automatic implementation
decode
Decoding 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
$KeyedDecodingContainer
Temporary constant ofcontainer
- in
PWT
We found it in the protocol witness listcontainer
Method 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. Container
Look at the Decodable protocol,Decoder
Exists in the protocolcontainer
Method declarationView the source code_JSONDecoder
thecontainer
Method, 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, passpython
Control, 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
- call
box_
Method wrapped as a dictionary type - use
JSONSerialization
Serialized asData
data
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 follows
Codable
Protocol, so the system automatically generated for the parent classencode(to encoder: Encoder)
methods - Subclasses inherit from the parent class, but are not overridden
encode(to encoder: Encoder)
methods - So in the process of coding, you still find the parent class
encode
Method, 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 followsCodable
Protocol, still compiling error, hint: protocol type does not matchDecodable
Protocol, 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 boundsObjectMapper
: Manually provide the mapping for each object, which is a lot of codeHandyJSON
: The use of memory assignment method for codec operation, principle andCodable
All roads lead to RomeCodable
: Encounter inheritance and polymorphic mode, need to manually implement the codec function
HandyJSON and Codable have an edge over other libraries