用不起:

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

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

并且模型的特点想要默认值,无。。。

Swift是时候使用Codable了

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

所以网络一边夸他Codable好用,一边真实工程开发中却还用不起来。

搞起来:

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

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

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

Swift是时候使用Codable了

他的模型界说能够简化为:

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。

Swift是时候使用Codable了

模型改为如下,运用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