✨✨✨看起来还不错?给个star✨吧,急需支持✨✨✨
🌐 If you need,please visit >>> English README
SmartCodable 是一个基于Swift的Codable协议的数据解析库,旨在提供更为强大和灵活的解析能力。通过优化和重写Codable的标准功能,SmartCodable 有效地解决了传统解析过程中的常见问题,并提高了解析的容错性和灵活性。
【✅: 完美支持】【
序号 | 🎯 特性 | 💬 特性说明 💬 | SmartCodable | HandyJSON |
---|---|---|---|---|
1 | 强大的兼容性 | 完美兼容:字段缺失 & 字段值为nul & 字段类型错误 | ✅ | ✅ |
2 | 类型自适应 | 如JSON中是一个Int,但对应Model是String字段,会自动完成转化 | ✅ | ✅ |
3 | 解析Any | 支持解析 [Any], [String: Any] 等类型 | ✅ | ✅ |
4 | 解码回调 | 支持Model解码完成的回调,即:didFinishingMapping | ✅ | ✅ |
5 | 属性初始化值填充 | 当解析失败时,支持使用初始的Model属性的赋值。 | ✅ | ✅ |
6 | 字符串的Model化 | 字符串是json字符串,支持进行Model化解析 | ✅ | ✅ |
7 | 枚举的解析 | 当枚举解析失败时,支持兼容。 | ✅ | ✅ |
8 | 属性的自定义解析 - 重命名 | 自定义解码key(对解码的Model属性重命名) | ✅ | ✅ |
9 | 属性的自定义解析 - 忽略 | 忽略某个Model属性的解码 | ✅ | ✅ |
10 | 支持designatedPath | 实现自定义解析路径 | ✅ | ✅ |
11 | Model的继承 | 在model的继承关系下,Codable的支持力度较弱,使用不便(可以支持) | ❌ | ✅ |
12 | 自定义解析路径 | 指定从json的层级开始解析 | ✅ | ✅ |
13 | 超复杂的数据解码 | 解码过程中,多数据做进一步的整合/处理。如: 数据的扁平化处理 | ✅ | |
14 | 解码性能 | 在解码性能上,SmartCodable 平均强 30% | ✅ | |
15 | 异常解码日志 | 当解码异常进行了兼容处理时,提供排查日志 | ✅ | ❌ |
16 | 安全性方面 | 底层实现的稳定性和安全性。 | ✅ | ❌ |
17 | 支持designatedPath | 实现自定义解析路径 | ✅ | ✅ |
整体来讲: SmartCodable 和 HandyJSON 相比,在功能和使用上相近。
-
HandyJSON 使用Swift的反射特性来实现数据的序列化和反序列化。该机制是非法的,不安全的, 更多的细节请访问 HandyJSON 的466号issue.
-
Codable 是Swift标准库的一部分,提供了一种声明式的方式来进行序列化和反序列化,它更为通用。
有不少使用者提出了优化需求 或 新功能的要求。在这边逐一回复:
💡 建议列表 | 是否采纳 | 理由 |
---|---|---|
① #suggest 1 在mapping方法中支持解析忽略 | ❌ | 不采纳的理由 |
② #suggest 2 像HandyJSON一样支持继承关系的解析 | ❌ | 不采纳的理由 |
③ #suggest 3 支持初始值填充 | ✅ | 实现逻辑 |
④ #suggest 4 提供HandyJSON的替换指导 | ✅ | 替换指导 |
⑤ #suggest 5 提供全局的Key映射策略 | ✅ | 实现逻辑 |
⑥ #suggest 6 支持UIColor的解析 | ✅ | 实现逻辑 |
⑦ #suggest 7 增加单个Value的自定义转换策略 | ✅ | 实现逻辑 |
⑧ #suggest 8 支持designatedPath的自定义路径解析 | ✅ | 参考HandyJSON实现 |
更多内容请查看: 替换指导
内容项 | 内容项说明 | 使用场景 | 替换难度 | 评判理由 |
---|---|---|---|---|
①声明Model | 声明Model | ★★★★★ | ★☆☆☆☆ | 全局将 HandyJSON 替换为 SmartCodable即可。 |
②反序列化 | 数据的模型化(数据转Model) | ★★★★★ | ☆☆☆☆☆ | 完全一样的调用方式,无需处理。 |
③序列化 | 模型的数据化(Model转数据) | ★☆☆☆☆ | ★☆☆☆☆ | 将 toJSON() 替换为 toDictionary() 或 toArray() 。 |
④解码完成的回调 | 解析完成进一步处理数据 | ★★☆☆☆ | ☆☆☆☆☆ | 完全一样的调用方式,无需处理。 |
⑤自定义解析Key | 忽略key的解析 & 自定义Key的映射关系 | ★★★☆☆ | ★★★☆☆ | 需要更改调用方式。 |
⑥解析Any | 解析Any类型的数据。Any,[String: Any], [Any] | ★☆☆☆☆ | ★☆☆☆☆ | 将Any替换为SmartAny |
⑦处理继承关系 | 解析存在的继承关系的Model | ★☆☆☆☆ | ★★★★★ | 建议使用协议实现。 |
⑧枚举的解析 | 解析枚举属性 | ★☆☆☆☆ | ★☆☆☆☆ | 多实现一个 defaultCase |
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '11.0'
use_frameworks!
target 'MyApp' do
pod 'SmartCodable'
end
https://github.com/intsig171/SmartCodable.git
import SmartCodable
struct Model: SmartCodable {
var name: String = ""
}
let dict: [String: String] = ["name": "xiaoming"]
guard let model = Model.deserialize(from: dict) else { return }
import SmartCodable
struct Model: SmartCodable {
var name: String = ""
}
let dict: [String: String] = ["name": "xiaoming"]
let arr = [dict, dict]
guard let models = [Model].deserialize(from: arr) else { return }
// 字典转模型
guard let xiaoMing = JsonToModel.deserialize(from: dict) else { return }
// 模型转字典
let studentDict = xiaoMing.toDictionary() ?? [:]
// 模型转json字符串
let json1 = xiaoMing.toJSONString(prettyPrint: true) ?? ""
// json字符串转模型
guard let xiaoMing2 = JsonToModel.deserialize(from: json1) else { return }
Codable是无法解码Any类型的,这样就意味着模型的属性类型不可以是 Any,[Any],[String: Any]等类型, 这对解码造成了一定的困扰。SmartAny 是SmartCodable 提供的解决Any的方案。可以直接像使用 Any 一样使用它。
struct AnyModel: SmartCodable {
var name: SmartAny?
var dict: [String: SmartAny] = [:]
var arr: [SmartAny] = []
}
let dict = [
"name": "xiao ming",
"age": 20,
"dict": inDict,
"arr": arr
] as [String : Any]
guard let model = AnyModel.deserialize(from: dict) else { return }
print(model.name.peel )
print(model.dict.peel)
print(model.arr.peel)
真实的数据被 SmartAny 包裹住了,需要使用 peel 对数据解包。
同时也提供了反向转换的方法:
From | To | Example |
---|---|---|
Any |
SmartAny |
SmartAny(from: "some") |
[String: Any] |
[String: SmartAny] |
["key2": "value2"].cover |
[Any] |
[SmartAny] |
[ ["key3": "value3"] ].cover |
let dict: [String: Any] = [
"hobby": "{\"name\":\"sleep\"}",
]
guard let model = Model.deserialize(from: dict) else { return }
print(model)
struct Model: SmartCodable {
var hobby: Hobby?
}
struct Hobby: SmartCodable {
var name: String = ""
}
let dict = [
"color": "7DA5E3"
]
struct Model: SmartCodable {
var color: SmartColor?
}
guard let model = Model.deserialize(from: dict) else { return }
print(model.color?.peel)
UIColor 是 non-final class
。非最终类不能简单地实现Codable
的init(from:)
。具体可查阅 suggest 6。
让枚举遵循 SmartCaseDefaultable ,当解码失败时使用 defaultCase。
struct CompatibleEnum: SmartCodable {
var enumTest: TestEnum?
enum TestEnum: String, SmartCaseDefaultable {
case a
case b
case c = "hello"
}
}
完全交给你自定义解析规则。 如果不自定义,不进行解析。
/// 关联值枚举的解析, 需要自己接管decode
enum Sex: SmartAssociatedEnumerable {
static var defaultCase: Sex = .women
case man
case women
case other(String)
}
struct CompatibleEnum: SmartCodable {
var sex: Sex = .man
static func mappingForValue() -> [SmartValueTransformer]? {
[
CodingKeys.sex <--- RelationEnumTranformer()
]
}
}
struct RelationEnumTranformer: ValueTransformable {
func transformToJSON(_ value: Introduce_8ViewController.Sex?) -> String? {
guard let value = value else { return nil }
switch value {
case .man:
return "man"
case .women:
return "women"
case .other(let v):
return v
}
}
typealias Object = Sex
typealias JSON = String
func transformFromJSON(_ value: Any?) -> Sex? {
guard let temp = value as? String else { return .man }
switch temp {
case "man":
return .man
case "women":
return .women
default:
return .other(temp)
}
}
}
解码策略分为三个阶段的操作:
- 解码前
- 忽略某些key的解析
- 解码中
- Key的映射策略
- Value的解析策略
- 解码后
- 解码完成后的回调
struct Model: SmartCodable {
var name: String = ""
var ignore: String = ""
var age: Int = 0
enum CodingKeys: String, CodingKey {
case name
case age = "selfAge"
// 忽略ignore的解析。
// case ignore
}
}
重写当前的 CodingKeys,不需要解析谁,就删掉谁。留下的是需要解析的。
当然,也可以在这里进行key的重命名。
也可以选择使用 @IgnoredKey 属性包装器忽略某个Key的解析。
struct Home: SmartCodable {
var name: String = ""
@IgnoredKey
var age: [Any] = ["1"]
@IgnoredKey
var area: String = "area"
}
严格意义上来说: 要忽略解析只能通过重写 CodingKeys。 作者通过**@IgnoredKey** 的标记打断了解析过程(还是会进入解析 ),返回了该属性的初始化值。
public enum SmartKeyDecodingStrategy : Sendable {
case useDefaultKeys
// 蛇形命名转驼峰命名
case fromSnakeCase
// 首字母大写转小写
case firstLetterLower
}
作用于本次解析,本次解析只能使用一种策略,不可混合使用。
let option1: SmartDecodingOption = .key(.fromSnakeCase)
guard let model1 = TwoModel.deserialize(from: dict1, options: [option1]) else { return }
- 支持自定义路径解析。
- 支持字段解析的重命名。
跨层解析。将sub里面的name字段,解析到 Model的name属性上。
let dict = [
"age": 10,
"sub": [
"name": "Mccc"
]
]
struct Model: SmartCodable {
var age: Int = 0
var name: String = ""
static func mappingForKey() -> [SmartKeyTransformer]? {
[ CodingKeys.name <--- "sub.name" ]
}
}
支持自定义映射关系,你需要实现一个可选的mapping
函数。
struct Model: SmartCodable {
var name: String = ""
var age: Int = 0
var ignoreKey: String?
enum CodingKeys: CodingKey {
case name
case age
}
static func mappingForKey() -> [SmartKeyTransformer]? {
[
CodingKeys.name <--- ["nickName", "realName"],
CodingKeys.age <--- "person_age"
]
}
}
-
1对1 的映射
你可以选择像
CodingKeys.age <--- "person_age"
,只处理1对1的映射。 -
1对多 的映射
也可以像
CodingKeys.name <--- ["nickName", "realName"]
一样处理 1对多 的映射。如果恰好都有值,将选择第一个。
SmartDecodingOption提供了三种解码选项,分别为:
public enum SmartDecodingOption {
/// 用于解码 “Date” 值的策略
case dateStrategy(JSONDecoder.DateDecodingStrategy)
/// 用于解码 “Data” 值的策略
case dataStrategy(JSONDecoder.DataDecodingStrategy)
/// 用于不符合json的浮点值(IEEE 754无穷大和NaN)的策略
case floatStrategy(JSONDecoder.NonConformingFloatDecodingStrategy)
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let option: JSONDecoder.SmartDecodingOption = .dateStrategy(.formatted(dateFormatter))
guard let model = FeedOne.deserialize(from: json, options: [option]) else { return }
let option: JSONDecoder.SmartDecodingOption = .dataStrategy(.base64)
guard let model = FeedOne.deserialize(from: json, options: [option]) else { return }
gurad let data = model.address, let url = String(data: data, encoding: .utf8) { else }
let option: JSONDecoder.SmartDecodingOption = .floatStrategy(.convertFromString(positiveInfinity: "infinity", negativeInfinity: "-infinity", nan: "NaN"))
guard let model1 = FeedOne.deserialize(from: json, options: [option]) else { return }
struct SmartModel: SmartCodable {
var date1: Date?
var date2: Date?
var url: URL?
var data: Data?
// value的解析策略
static func mappingForValue() -> [SmartValueTransformer]? {
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
return [
CodingKeys.url <--- SmartURLTransformer(prefix: "https://"),
CodingKeys.date2 <--- SmartDateTransformer(),
CodingKeys.date1 <--- SmartDateFormatTransformer(format),
CodingKeys.data <--- SmartDataTransformer()
]
}
}
您可以实现 mappingForValue
给每个属性设置不同的解析策略。
支持的类型:
- Date
- UIColor
- URL
- Data
如需其他类型可以提 issue。
遵守该协议,实现协议方法。
public protocol ValueTransformable {
associatedtype Object
associatedtype JSON
/// transform from ’json‘ to ’object‘
func transformFromJSON(_ value: Any?) -> Object?
/// transform to ‘json’ from ‘object’
func transformToJSON(_ value: Object?) -> JSON?
}
class Model: SmartDecodable {
var name: String = ""
var age: Int = 0
var desc: String = ""
required init() { }
// 解析完成的回调
func didFinishMapping() {
if name.isEmpty {
desc = "\(age)岁的" + "人"
} else {
desc = "\(age)岁的" + name
}
}
}
出现 SmartLog Error 日志代表着 SmartCodable 遇到了解析问题,走进了兼容。 并不代表着本次解析失败。
SmartCodable鼓励从根本上解决解析中的问题,即:不需要用到SmartCodable的兼容逻辑。 如果出现解析兼容的情况,修改Model中属性的定义,或要求数据方进行修正。为了更方便的定位问题。
调试日志,将提供辅助信息
======================== [Smart Decoding Log] ========================
Family 👈🏻 👀
|- name : Expected to decode String but found an array instead.
|- location: Expected to decode String but found an array instead.
|- date : Expected to decode Date but found an array instead.
|> father: Father
|- name: Expected String value but found null instead.
|- age : Expected to decode Int but found a string/data instead.
|> dog: Dog
|- hobby: Expected to decode String but found a number instead.
|> sons: [Son]
|- [Index 0] hobby: Expected to decode String but found a number instead.
|- [Index 0] age : Expected to decode Int but found a string/data instead.
|- [Index 1] age : Expected to decode Int but found an array instead.
=========================================================================
新版本的日志:
-
以Model为单元进行和合并,减少了日志的数量。
-
合并相同的解析错误,减少了相同日志的干扰。
-
简化了日志内容,让日志一目了然。
我们提供了详细的示例工程,可以下载工程代码查看。
这是Swift数据解析方案的系列文章:
Swift数据解析(第四篇) - SmartCodable 上
Swift数据解析(第四篇) - SmartCodable 下
SmartCodable 是一个开源项目,我们欢迎所有对提高数据解析性能和健壮性感兴趣的开发者加入。无论是使用反馈、功能建议还是代码贡献,你的参与都将极大地推动 SmartCodable 项目的发展。