Codable常见用法
将json的弱类型和本身类的强类型之间相互转换
public typealias Codable = Decodable & Encodable
嵌套的模型
struct Teacher: Codable{
var name: String
var className: String
var courceCycle: Int
var personInfo: PersonInfo
}
extension Teacher {
struct PersonInfo: Codable {
var age: Int
var height: Double
}
}
let jsonString = """
{
"name": "Kody",
"className": "Swift",
"courceCycle": 10,
"personInfo": {
"age": 18,
"height": 1.85
}
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
let result = try? decoder.decode(Teacher.self, from: data)
print(result ?? "解析失败")
}
·················
Teacher(name: "Kody", className: "Swift", courceCycle: 10, personInfo: LGSwiftTest.Teacher.PersonInfo(age: 18, height: 1.85))
JSON数据中包含数组
struct Teacher: Codable{
var name: String
var className: String
var courceCycle: Int
var personInfo: [PersonInfo]
}
extension Teacher {
struct PersonInfo: Codable {
var age: Int
var height: Double
}
}
let jsonString = """
{
"name": "Kody",
"className": "Swift",
"courceCycle": 10,
"personInfo": [
{
"age": 18,
"height": 1.85
},{
"age": 20,
"height": 1.75
}
]
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
let result = try? decoder.decode(Teacher.self, from: data)
print(result ?? "解析失败")
}
···········
Teacher(name: "Kody", className: "Swift", courceCycle: 10, personInfo: [LGSwiftTest.Teacher.PersonInfo(age: 18, height: 1.85), LGSwiftTest.Teacher.PersonInfo(age: 20, height: 1.75)])
JSON数据是一组数组集合
struct Teacher: Codable{
var name: String
var className: String
var courceCycle: Int
}
let jsonString = """
[
{
"name": "Kody",
"className": "Swift",
"courceCycle": 12
},{
"name": "Cat",
"className": "强化班",
"courceCycle": 15
},{
"name": "Hank",
"className": "逆向班",
"courceCycle": 22
},{
"name": "Cooci",
"className": "大师班",
"courceCycle": 22
}
]
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
let result = try? decoder.decode([Teacher].self, from: data)
print(result ?? "解析失败")
}
·················
[LGSwiftTest.Teacher(name: "Kody", className: "Swift", courceCycle: 12), LGSwiftTest.Teacher(name: "Cat", className: "强化班", courceCycle: 15), LGSwiftTest.Teacher(name: "Hank", className: "逆向班", courceCycle: 22), LGSwiftTest.Teacher(name: "Cooci", className: "大师班", courceCycle: 22)]
在decode
时传输数组类型
JSON数据中有 Optional values
struct Teacher: Codable{
var name: String
var className: String?
var courceCycle: Int
}
let jsonString = """
{
"name": "Cat",
"className": null,
"courceCycle": 15
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
let result = try? decoder.decode(Teacher.self, from: data)
print(result ?? "解析失败")
}
将可能出现问题的属性设置为可选值即可
元组类型
struct Location: Codable {
var x: Double
var y: Double
init(from decoder: Decoder) throws{
var contaioner = try decoder.unkeyedContainer()
self.x = try contaioner.decode(Double.self)
self.y = try contaioner.decode(Double.self)
}
}
struct RawSeverResponse: Codable{
var location: Location
}
let jsonString = """
{
"location": [20, 10]
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(RawSeverResponse.self, from: jsonData!)
print(result.location.x)
在init中进行单独处理
继承
class LGTeacher: Codable {
var name: String?
}
class LGPartTimeTeacher: LGTeacher {
var partTime: Int?
}
let jsonString = """
{
"name": "Kody",
"partTime": 20
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(LGPartTimeTeacher.self, from: jsonData!)
print(result.name)
父类实现Codable
协议即可
不方便的数组类型
struct LGPerson: Decodable{
let elements: [String]
enum CodingKeys: String, CaseIterable, CodingKey {
case item0 = "item.0"
case item1 = "item.1"
case item2 = "item.2"
case item3 = "item.3"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var element: [String] = []
for item in CodingKeys.allCases{
guard container.contains(item) else { break }
element.append(try container.decode(String.self, forKey: item))
}
self.elements = element
}
}
//
//
let jsonString = """
{
"item.3": "Kody",
"item.0": "Hank",
"item.2": "Cooci",
"item.1": "Cat"
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(LGPerson.self, from: jsonData!)
print(result)
Codable源码解析
public typealias Codable = Decodable & Encodable
//解码协议
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
}
//编码协议
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
}
解码处理流程
- 日期解码
当前我们创建一个解码的对象,然后调用 decode 方法将我们的 json 字符串解析给我们的模型 。这里我们需要探究的是它究竟是如何工作的?
public enum DateDecodingStrategy {
/// Defer to `Date` for decoding. This is the default strategy.
case deferredToDate
/// 代表距离 1970.01.01 的秒数
/// Decode the `Date` as a UNIX timestamp from a JSON number.
case secondsSince1970
/// 代表距离 1970.1.1 的毫秒数
/// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
case millisecondsSince1970
/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
case iso8601
/// 后台自定义的格式,这个时候我们可以自己创建 DateFormatter,来解析
/// Decode the `Date` as a string parsed by the given formatter.
case formatted(DateFormatter)
/// 自定义格式
/// Decode the `Date` as a custom value decoded by the given closure.
case custom((Decoder) throws -> Date)
}
这里我们来看一下实际的使用场景:
struct LGTeacher: Codable {
var name: String
var className: String
var courceCycle: Int
var date: Date
}
let jsonString = """
{
"name": "Kody",
"className": "Swift",
"courceCycle": 10
"date": "1969-09-26T12:00:00Z"
}
"""
如果我们直接使用默认的解析策略,那么这里代码运行之后,就会出现解析失败
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.iso8601
let jsonString = """
{
"name": "Kody",
"className": "Swift",
"courceCycle": 10,
"date": 1609183207
}
"""
decoder.dateDecodingStrategy = .secondsSince1970
let jsonString = """
{
"name": "Kody",
"className": "Swift",
"courceCycle": 10,
"date": 1609183207000
}
"""
decoder.dateDecodingStrategy = .millisecondsSince1970
let jsonString = """
{
"name": "Kody",
"className": "Swift",
"courceCycle": 10,
"date": "2020/12/28 19:20:00"
}
"""
这种后台自定义的格式,需要我们创建一个DateFormatter
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
- 二进制解码
/// The strategy to use for decoding `Data` values.
public enum DataDecodingStrategy {
/// Defer to `Data` for decoding.
case deferredToData
/// Decode the `Data` from a Base64-encoded string. This is the default strategy.
case base64
/// Decode the `Data` as a custom value decoded by the given closure.
case custom((_ decoder: Decoder) throws -> Data)
}
- 不合法浮点数的编码策略
public enum NonConformingFloatDecodingStrategy
- key的编码策略
public enum KeyDecodingStrategy
接下来我们实际看一下当前是如何 decode 的
// MARK: - Decoding Values
/// Decodes a top-level value of the given type from the given JSON representation.
///
/// - parameter type: The type of the value to decode.
/// - parameter data: The data to decode from.
/// - returns: A value of the requested type.
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
/// - throws: An error if any value throws an error during decoding.
open func decode(_ 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))
}
//创建decoder对象
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
}
- 这里是一个泛型函数,传入的参数
T
要求遵守Decodable
协议。 - 调用
JSONSerializationg
对当前data
进行序列话的操作 - 调用内部类
_JSONDecoder
创建一个对象,然后调用unBox
解码
fileprivate class _JSONDecoder : Decoder {
......
// MARK: - Initialization
/// Initializes `self` with the given top-level container and options.
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
}
fileprivate func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
#if DEPLOYMENT_RUNTIME_SWIFT
// Bridging differences require us to split implementations here
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() }
//LGTeacher,编译器自动帮我们生成的
//_JSONDecoder
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
}
通过不同的策略来进行处理
fileprivate func unbox(_ 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
}
递归解码,然后通过type.init(from: self)
创建对象进行赋值.
通过sil文件我们可以看到底层调用了
public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer {
guard !(self.storage.topContainer is NSNull) else {
throw DecodingError.valueNotFound(KeyedDecodingContainer.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(referencing: self, wrapping: topContainer)
return KeyedDecodingContainer(container)
}
返回的一个KeyedDecodingContainer
类,根据Codable.swift.gyb
模板来生成文件进行解码
所以let result = try? decoder.decode(LGTeacher.self, from: data)
做的就是
struct LGTeacher: Codable {
var name: String
var className: String
var courceCycle: Int
init(from decoder: Decoder) throws{
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey:.name )
self.className = try container.decode(String.self, forKey:.className )
self.courceCycle = try container.decode(Int.self, forKey:.courceCycle )
}
}
编码流程处理
就是解码流程反过来操作
struct Teacher: Codable {
var name: String
var className: String
var courceCycle: Int
}
let teacher = Teacher(name: "kk", className: "IT", courceCycle: 20)
let encoder = JSONEncoder()
let data = try! encoder.encode(teacher)
let str = String(data: data, encoding: .utf8)!
print(str)
···········
{"name":"kk","className":"IT","courceCycle":20}
open func encode(_ 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))
}
}
返回一个编码方法
Codable坑点分析
class LGPerson: Codable {
var name: String?
var age: Int?
}
class LGTeacher: LGPerson {
var subjectName: String?
}
class LGPartTimeTeacher: LGPerson{
var partTime: Double?
}
let t = LGTeacher()
t.age = 10
t.name = "Kody"
t.subjectName = "Swift"
let encoder = JSONEncoder()
let encoderData = try encoder.encode(t)
可以看到当前仅仅能正常编码成功我们的 age
和 name
,但是我们 subjectName
没法正常编码.
那这里的 encode
方法在我们遵守 Codable 协议之后,系统自动帮助我们实现了,通过 SIL可以知道父类实现了encode
方法,子类直接调用了父类方法,所以我们要覆盖父类方法,但是这样写的话,就会存在另一个问题,因为当前的 CodingKeys 访问不到,所以这里我们就需要这样操作
class LGTeacher: LGPerson {
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)
}
}
protocol LGPerson: Codable {
var age: String { get set }
var name: String { get set }
}
struct LGTeacher: LGPerson {
var age: String
var name: String
}
struct LGParTimeTeacher: LGPerson {
var age: String
var name: String
}
struct Company: Codable{
var person: [LGPerson]
var companyName: String
enum CodingKeys: String, CodingKey {
case person
case companyName
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(person, forKey: .person)
try container.encode(companyName, forKey: .companyName)
}
}
当前编译器就直接报错了,因为当前数组中的 LGPerson
是个协议,他不能遵守他自身
这个时候我们可能想到的是直接在 LGTeacher
, LGPartTimeTeacher
中实现 decode 和 encode
的方法,那我们可以找一个中间层来解决这个问题:
protocol LGPerson{
var age: String { get set }
var name: String { get set }
}
struct LGTeacher: LGPerson {
var age: String
var name: String
}
struct LGParTimeTeacher: LGPerson {
var age: String
var name: String
}
struct LGPersonBox : LGPerson, Codable {
var age: String
var name: String
init(_ person: LGPerson) {
self.age = person.age
self.name = person.name
}
}
struct Company: Codable{
var person: [LGPersonBox]
var companyName: String
}
protocol LGPerson{
var age: Int { get set }
var name: String { get set }
}
struct LGTeacher: LGPerson {
var age: Int
var name: String
}
struct LGParTimeTeacher: LGPerson {
var age: Int
var name: String
}
struct LGPersonBox : LGPerson, Codable {
var age: Int
var name: String
init(_ person: LGPerson) {
self.age = person.age
self.name = person.name
}
}
struct Company: Codable{
var person: [LGPersonBox]
var companyName: String
}
let person: [LGPerson] = [LGTeacher(age: 20, name: "Kody"),LGParTimeTeacher(age: 30, name: "Hank")]
let company = Company(person: person.map(LGPersonBox.init), companyName: "Logic")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(company)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
可以看到这里输出的格式都是 LGPersonBox
,如果我们想正确的还原我们当前的类型信息,应该做什么哪?很简单的一种做法就是需要在编码过程中将我们的类型信息编码进去,什么意思那?我们通过代码来看一下:
enum LGPersonType:String, Codable {
case teacher
case partTeacher
var metdadata: LGPerson.Type {
switch self {
case .teacher:
return LGTeacher.self
case .partTeacher:
return LGParTimeTeacher.self
}
}
}
protocol LGPerson: Codable{
static var type: LGPersonType{ get }
var age: Int { get set }
var name: String { get set }
}
struct LGTeacher: LGPerson {
static var type: LGPersonType = LGPersonType.teacher
var age: Int
var name: String
}
struct LGParTimeTeacher: LGPerson {
static var type: LGPersonType = LGPersonType.partTeacher
var age: Int
var name: String
}
struct LGPersonBox : Codable {
var p: LGPerson
init(_ p: LGPerson) {
self.p = p
}
private enum CodingKeys : CodingKey {
case type
case p
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(LGPersonType.self, forKey: .type)
self.p = try type.metdadata.init(from: container.superDecoder(forKey: .p))
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type(of: p).type, forKey: .type)
try p.encode(to: container.superEncoder(forKey: .p))
}
}
struct Company: Codable{
var person: [LGPersonBox]
var companyName: String
}
let person: [LGPerson] = [LGTeacher(age: 20, name: "Kody"),LGParTimeTeacher(age: 30, name: "Hank")]
let company = Company(person: person.map(LGPersonBox.init), companyName: "Logic")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(company)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
另一个思路
protocol Meta: Codable {
associatedtype Element
static func metatype(for typeString: String) -> Self
var type: Decodable.Type { get }
}
struct MetaObject: Codable {
let object: M.Element
init(_ object: M.Element) {
self.object = object
}
enum CodingKeys: String, CodingKey {
case metatype
case object
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let typeStr = try container.decode(String.self, forKey: .metatype)
let metatype = M.metatype(for: typeStr)
let superDecoder = try container.superDecoder(forKey: .object)
let obj = try metatype.type.init(from: superDecoder)
guard let element = obj as? M.Element else {
fatalError()
}
self.object = element
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let typeStr = String(describing: type(of: object))
try container.encode(typeStr, forKey: .metatype)
let superEncoder = container.superEncoder(forKey: .object)
let encodable = object as? Encodable
try encodable?.encode(to: superEncoder)
}
}
enum LGPersonType: String, Meta {
typealias Element = LGPerson
case teacher = "LGTeacher"
case partTimeTeacher = "LGPartTimeTeacher"
static func metatype(for typeString: String) -> LGPersonType {
guard let metatype = self.init(rawValue: typeString) else {
fatalError()
}
return metatype
}
var type: Decodable.Type {
switch self {
case .teacher:
return LGTeacher.self
case .partTimeTeacher:
return LGPartTimeTeacher.self
}
}
}
class LGPerson: Codable {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
class LGTeacher: LGPerson {
var subjectName: String
init(name: String, age: Int, subjectName: String) {
self.subjectName = subjectName
super.init(name: name, age: age)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
subjectName = try container.decode(String.self, forKey: .subjectName)
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(subjectName, forKey: .subjectName)
let superdecoder = container.superEncoder()
try super.encode(to: superdecoder)
}
enum CodingKeys: String, CodingKey {
case subjectName
}
}
class LGPartTimeTeacher: LGPerson {
var partTime: Double
init(name: String, age: Int, partTime: Double) {
self.partTime = partTime
super.init(name: name, age: age)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
partTime = try container.decode(Double.self, forKey: .partTime)
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(partTime, forKey: .partTime)
let superdecoder = container.superEncoder()
try super.encode(to: superdecoder)
}
enum CodingKeys: String, CodingKey {
case partTime
}
}
let p: LGPerson = LGTeacher(name: "Kody", age: 20, subjectName: "Swift")
let jsonData = try JSONEncoder().encode(MetaObject(p))
if let str = String(data: jsonData, encoding: .utf8) {
print(str)
}
let decode: MetaObject = try JSONDecoder().decode(MetaObject.self, from: jsonData)