Swift是时候使用Codable了

用不起:

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

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

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

image.png

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

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

搞起来:

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

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

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

image.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。

image.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

猜你喜欢

转载自juejin.im/post/7168748765946806303