注册

Swift是时候使用Codable了

用不起:


苹果发布Swift支持Codable已经有一定历史年限了,为什么还用不起来,无非就是苹果的Codable太强势了,


比如模型里的定义比数据返回的json多一个key,少一个key,key的值类型不匹配(如定义为String,返回的是Int),苹果老子直接掀桌子,整个模型为nil。这。。。


而且模型的属性想要默认值,无。。。


bb4439bd133720512896ac5e0c2c133a.png


你牛,牛到大家不知道怎么用


于是网络一边夸他Codable好用,一边真正工程开发中却还用不起来。


搞起来:


最近研究网上有没有好用的Codable库的时候,找到了这个。2021 年了,Swift 的 JSON-Model 转换还能有什么新花样github.com/iwill/ExCod…


经过他的封装,把苹果包装的服服帖帖。经测试,解决如下问题:

  1. 多一个key
  2. 少一个key
  3. key的类型不匹配的时候,自动做类型转换
  4. 默认值处理好。 

e143506393892b2b49696a938e874a00.png


他的模型定义可以简化为:

struct testModel: ExAutoCodable {
@ExCodable
var courseId: Int = -1
@ExCodable
var totalSectionCount: Int = -1 // 总的章节
@ExCodable
var courseImageUrl: String = ""
@ExCodable
var tudiedSectionCount: Int = 0 // 已经学习章节
}

既然他这么好,那就用起来啰喂,,,,等等,等等


定义模型这样,竟然不行:

struct testModel: ExAutoCodable {
@ExCodable
var jumpParam: [String: Any]? = [:]

@ExCodable
var matchs: [Any] = []
}

苹果老子说Any不支持Codable???转模型的时候,这个全是空,nil。


一看工程,基本每个模型的定义都有这个呀,全有Any的定义,懵逼


研究起来:


通过研究stackoverflow.com/questions/4…, 发现可以给Any封装一个支持Codable的类型,比如AnyCodable这样。然后模型里面用到Any的,全部给换成AnyCodable。


ed72232b03f27fb98bce1eafd70b6b42.png


模型改为如下,使用AnyCodable

struct testModel: ExAutoCodable {
@ExCodable
var jumpParam: [String: AnyCodable]? = [:]

@ExCodable
var matchs: [AnyCodable] = []
}

AnyCodable.swift代码如下:

//
// AnyCodable.swift
//
// 因为Any不支持Codable,但是模型里面经常会用到[String: Any]。
// 所以添加类AnyCodable,代替Any,来支持Codable, 如:[String: AnyCodable]。
// https://stackoverflow.com/questions/48297263/how-to-use-any-in-codable-type

import Foundation

public struct AnyCodable: Decodable {
var value: Any

struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}

init(value: Any) {
self.value = value
}

public init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
var result = [String: Any]()
try container.allKeys.forEach { (key) throws in
result[key.stringValue] = try container.decode(AnyCodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyCodable.self).value)
}
value = result
} else if let container = try? decoder.singleValueContainer() {
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
}
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
}
}
}

extension AnyCodable: Encodable {
public func encode(to encoder: Encoder) throws {
if let array = value as? [Any] {
var container = encoder.unkeyedContainer()
for value in array {
let decodable = AnyCodable(value: value)
try container.encode(decodable)
}
} else if let dictionary = value as? [String: Any] {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dictionary {
let codingKey = CodingKeys(stringValue: key)!
let decodable = AnyCodable(value: value)
try container.encode(decodable, forKey: codingKey)
}
} else {
var container = encoder.singleValueContainer()
if let intVal = value as? Int {
try container.encode(intVal)
} else if let doubleVal = value as? Double {
try container.encode(doubleVal)
} else if let boolVal = value as? Bool {
try container.encode(boolVal)
} else if let stringVal = value as? String {
try container.encode(stringVal)
} else {
throw EncodingError.invalidValue(value, EncodingError.Context.init(codingPath: [], debugDescription: "The value is not encodable"))
}
}
}
}

这个结合Excodable,经过测试,完美。数据转换成功。


如果模型的定义忘记了,还是定义为Any呢。 再给Excodable库里面的源码,做安全检查,修改代码如下:

public extension Encodable {
func encode(to encoder: Encoder, nonnull: Bool, throws: Bool) throws {
var mirror: Mirror! = Mirror(reflecting: self)
while mirror != nil {
for child in mirror.children where child.label != nil {
try (child.value as? EncodablePropertyWrapper)?.encode(to: encoder, label: child.label!.dropFirst(), nonnull: false, throws: false)
// 注意:Any不支持Codable, 可以使用AnyCodable代替。
// 注意枚举类型,要支持Codable
assert((child.value as? EncodablePropertyWrapper) != nil, "模型:\(mirror)里面的属性:\(child.label) 需要支持 Encodable")
}
mirror = mirror.superclassMirror
}
}
}

public extension Decodable {
func decode(from decoder: Decoder, nonnull: Bool, throws: Bool) throws {
var mirror: Mirror! = Mirror(reflecting: self)
while mirror != nil {
for child in mirror.children where child.label != nil {
try (child.value as? DecodablePropertyWrapper)?.decode(from: decoder, label: child.label!.dropFirst(), nonnull: false, throws: false)
// 注意:Any不支持Codable, 可以使用AnyCodable代替。
// 注意枚举类型,要支持Codable
assert((child.value as? DecodablePropertyWrapper) != nil, "模型:\(mirror)里面的属性:\(child.label) 需要支持 Decodable")
}
mirror = mirror.superclassMirror
}
}
}

嗯,这下模型如果定义为Any,可以在运行的时候报错,提醒要改为AnyCodable。


能愉快的编码了。。。


不过总感觉还差点东西。


再研究起来:


找到这个 github.com/levantAJ/An…


可以实现

let dictionary: [String: Any] = try container.decode([String: Any].self, forKey: key)
let array: [Any] = try container.decode([Any].self, forKey: key)

通过自定义[String: Any]和[Any]的解码,实现Any的Codble。


是否可以把这个合并到Excodable里面吧,从而什么都支持了,666。


在Excodable里面提issues,作者回复有空可以弄弄。


我急用呀,那就搞起来。


花了九牛二虎,终于搞出下面兼容代码:

// Make `Any` support Codable, like: [String: Any], [Any]
fileprivate protocol EncodableAnyPropertyWrapper {
func encode<Label: StringProtocol>(to encoder: Encoder, label: Label, nonnull: Bool, throws: Bool) throws
}
extension ExCodable: EncodableAnyPropertyWrapper {
fileprivate func encode<Label: StringProtocol>(to encoder: Encoder, label: Label, nonnull: Bool, throws: Bool) throws {
if encode != nil { try encode!(encoder, wrappedValue) }
else {
let t = type(of: wrappedValue)
if let key = AnyCodingKey(stringValue: String(label)) {
if (t is [String: Any].Type || t is [String: Any?].Type || t is [String: Any]?.Type || t is [String: Any?]?.Type) {
var container = try encoder.container(keyedBy: AnyCodingKey.self)
try container.encodeIfPresent(wrappedValue as? [String: Any], forKey: key)
} else if (t is [Any].Type || t is [Any?].Type || t is [Any]?.Type || t is [Any?]?.Type) {
var container = try encoder.container(keyedBy: AnyCodingKey.self)
try container.encodeIfPresent(wrappedValue as? [Any], forKey: key)
}
}
}
}
}
fileprivate protocol DecodableAnyPropertyWrapper {
func decode<Label: StringProtocol>(from decoder: Decoder, label: Label, nonnull: Bool, throws: Bool) throws
}
extension ExCodable: DecodableAnyPropertyWrapper {
fileprivate func decode<Label: StringProtocol>(from decoder: Decoder, label: Label, nonnull: Bool, throws: Bool) throws {
if let decode = decode {
if let value = try decode(decoder) {
wrappedValue = value
}
} else {
let t = type(of: wrappedValue)
if let key = AnyCodingKey(stringValue: String(label)) {
if (t is [String: Any].Type || t is [String: Any?].Type || t is [String: Any]?.Type || t is [String: Any?]?.Type) {
let container = try decoder.container(keyedBy: AnyCodingKey.self)
if let value = try container.decodeIfPresent([String: Any].self, forKey: key) as? Value {
wrappedValue = value
}
} else if (t is [Any].Type || t is [Any?].Type || t is [Any]?.Type || t is [Any?]?.Type) {
let container = try decoder.container(keyedBy: AnyCodingKey.self)
if let value = try container.decodeIfPresent([Any].self, forKey: key) as? Value {
wrappedValue = value
}
}
}
}
}
}

再在他用的地方添加

// MARK: - Encodable & Decodable - internal

public extension Encodable {
func encode(to encoder: Encoder, nonnull: Bool, throws: Bool) throws {
var mirror: Mirror! = Mirror(reflecting: self)
while mirror != nil {
for child in mirror.children where child.label != nil {
if let wrapper = (child.value as? EncodablePropertyWrapper) {
try wrapper.encode(to: encoder, label: child.label!.dropFirst(), nonnull: false, throws: false)
} else { //添加
try (child.value as? EncodableAnyPropertyWrapper)?.encode(to: encoder, label: child.label!.dropFirst(), nonnull: false, throws: false)
}
}
mirror = mirror.superclassMirror
}
}
}

public extension Decodable {
func decode(from decoder: Decoder, nonnull: Bool, throws: Bool) throws {
var mirror: Mirror! = Mirror(reflecting: self)
while mirror != nil {
for child in mirror.children where child.label != nil {
if let wrapper = (child.value as? DecodablePropertyWrapper) {
try wrapper.decode(from: decoder, label: child.label!.dropFirst(), nonnull: false, throws: false)
} else { //添加
try (child.value as? DecodableAnyPropertyWrapper)?.decode(from: decoder, label: child.label!.dropFirst(), nonnull: false, throws: false)
}
}
mirror = mirror.superclassMirror
}
}
}


完美:


综上,终于可以让Excodable库支持[String: Any]和[Any]的Codable了,撒花撒花。


从而模型定义这样,也能自动编解码:

struct testModel: ExAutoCodable {
@ExCodable
var jumpParam: [String: Any]? = [:]

@ExCodable
var matchs: [Any] = []
}

针对这个库的更新修改,改到这github.com/yxh265/ExCo…


也把对应的更新提交给Excodable的作者了,期待合并。
(作者iwill说,用ExCodable提供的 ExCodableDecodingTypeConverter 协议来实现是否可行。
我看了,因为Any不支持Codable,所以要想用ExCodableDecodingTypeConverter协议,也得要大改。也期待作者出马添加这个功能。)


最后的使用方法:


引入如下:

pod 'ExCodable', :git => 'https://github.com/yxh265/ExCodable.git', :commit => '4780fb8'

模型定义:

struct TestStruct: ExAutoCodable {
@ExCodable // 字段和属性同名可以省掉字段名和括号,但 `@ExCodable` 还是没办法省掉
var int: Int = 0
@ExCodable("string", "str", "s", "nested.string") // 支持多个 key 以及嵌套 key 可以这样写
var string: String? = nil
@ExCodable
var anyDict: [String: Any]? = nil
@ExCodable
var anyArray: [Any] = []
}

编解码:

let test = TestStruct(int: 304, string: "Not Modified", anyDict: ["1": 2, "3": "4"], anyArray: [["1": 2, "3": "4"]])
let data = try? test.encoded() as Data?
let copy1 = try? data?.decoded() as TestStruct?
let copy2 = data.map { try? TestStruct.decoded(from: $0) }
XCTAssertEqual(copy1, test)
XCTAssertEqual(copy2, test)

引用:


2021 年了,Swift 的 JSON-Model 转换还能有什么新花样


github.com/iwill/ExCod…


stackoverflow.com/questions/4…


stackoverflow.com/questions/4…


Property wrappers in Swift和Codable


作者:清点游玩
链接:https://juejin.cn/post/7168748765946806303
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册