Skip to content

Commit 0a33e4b

Browse files
committed
Squashed commit of the following:
1 parent 378811b commit 0a33e4b

70 files changed

Lines changed: 2314 additions & 3057 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
xcuserdata/
2-
*.xcuserdata
2+
*.xcuserdata
3+
.DS_Store

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CodableWrapper.podspec

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
# 关于CodableWrapper
2+
3+
* Codable很好,但是有一些缺陷:比如严格要求数据源,定义为String给了Int就抛异常、支持自定义CodingKey但是写法十分麻烦、缺字段的情况下不适用Optional会抛异常而不是使用缺省值等等。
4+
* 之前发布过[PropertyWrapper版](https://github.com/winddpan/CodableWrapper/tree/0.3.3),主要使用PropertyWrapper标记属性来提高了Codable的使用体验,使用了几个比较tricky的黑科技,所以API也比市面上的同类库要简单。
5+
* 现在Swift5.9支持宏了,决定写一个没有任何tricky的宏版本的CodableWrapper。目前已开发完毕[CodableWrapper/swift5.9-macro](https://github.com/winddpan/CodableWrapper/tree/swift5.9-macro)
6+
7+
## 首先确定目标
8+
1. 支持缺省值,JSON缺少字段容错
9+
2. 支持 `String` `Bool` `Number` 等基本类型互转
10+
3. 驼峰大小写自动互转
11+
4. 自定义解析key
12+
5. 自定义解析规则 (Transformer)
13+
6. 方便的 `Codable Class` 子类
14+
15+
## 设计API
16+
* 定义宏:
17+
* `@Codable`
18+
* `@CodableSubclass`
19+
* `@CodingKey(..)`
20+
* `@CodingNestedKey(..)`
21+
* `@CodingTransformer(..)`
22+
23+
* 例子:
24+
```
25+
@Codable
26+
struct BasicModel {
27+
var defaultVal: String = "hello world"
28+
var defaultVal2: String = Bool.random() ? "hello world" : ""
29+
let strict: String
30+
let noStrict: String?
31+
let autoConvert: Int?
32+
33+
@CodingKey("hello")
34+
var hi: String = "there"
35+
36+
@CodingNestedKey("nested.hi")
37+
@CodingTransformer(StringPrefixTransform("HELLO -> "))
38+
var codingKeySupport: String
39+
40+
@CodingNestedKey("nested.b")
41+
var nestedB: String
42+
43+
var testGetter: String {
44+
nestedB
45+
}
46+
}
47+
```
48+
49+
## 手动实现这些目标特性
50+
想要宏自动生成代码,需要先弄清楚怎么手动实现这些目标特性。先想象一下编译器怎么生成Codable的实现的:
51+
52+
```
53+
struct BasicModel: Codable {
54+
var defaultVal: String = "hello world"
55+
}
56+
```
57+
58+
```
59+
// 编译器生成的
60+
struct BasicModel: Codable {
61+
var defaultVal: String = "hello world"
62+
63+
enum CodingKeys: String, CodingKey {
64+
case defaultVal
65+
}
66+
67+
init(from decoder: Decoder) throws {
68+
let container = try decoder.container(keyedBy: CodingKeys.self)
69+
self.defaultVal = try container.decode(String.self, forKey: .defaultVal)
70+
}
71+
72+
func encode(to encoder: Encoder) throws {
73+
var container = encoder.container(keyedBy: CodingKeys.self)
74+
try container.encode(self.defaultVal, forKey: .defaultVal)
75+
}
76+
}
77+
```
78+
79+
### 手动实现目标1:支持缺省值
80+
81+
```
82+
init(from decoder: Decoder) throws {
83+
let container = try decoder.container(keyedBy: CodingKeys.self)
84+
self.defaultVal = (try? container.decode(String.self, forKey: .defaultVal)) ?? "hello world"
85+
}
86+
```
87+
88+
### 手动实现目标2:支持 `String` `Bool` `Number` 等基本类型互转
89+
90+
```
91+
init(from decoder: Decoder) throws {
92+
let container = try decoder.container(keyedBy: CodingKeys.self)
93+
94+
if let value = try? container.decode(Int.self, forKey: .defaultVal) {
95+
self.defaultVal = String(value)
96+
} else if let value = try? container.decode(String.self, forKey: .defaultVal) {
97+
self.defaultVal = String(value)
98+
} else if let value = try? container.decode(Float.self, forKey: .defaultVal) {
99+
self.defaultVal = String(value)
100+
}
101+
// 各种基本类型尝试转换 else if ...
102+
else {
103+
        self.defaultVal = "hello world"
104+
    }
105+
}
106+
```
107+
108+
这么解析肯定不是个办法,这里结合了两个开源库的实现,第一步使用[AnyDecodable](https://github.com/Flight-School/AnyCodable/blob/master/Sources/AnyCodable/AnyDecodable.swift)解析出JSON内的数据,解析出来可能是任意基本类型。第二步使用[BuiltInBridgeType](https://github.com/alibaba/HandyJSON/blob/master/Source/BuiltInBridgeType.swift)和[BuiltInBridgeType](https://github.com/alibaba/HandyJSON/blob/master/Source/BuiltInBridgeType.swift)将解析出来的基本类型尝试转换成目标类型。完整实现如下:
109+
110+
```swift
111+
extension Decodable {
112+
static func decode<K>(from container: KeyedDecodingContainer<K>, forKey key: KeyedDecodingContainer<K>.Key) throws -> Self {
113+
return try container.decode(Self.self, forKey: key)
114+
}
115+
}
116+
117+
struct BasicModel: Codable {
118+
var defaultVal: String = "hello world"
119+
120+
enum CodingKeys: CodingKey {
121+
case defaultVal
122+
}
123+
124+
init(from decoder: Decoder) throws {
125+
let container = try decoder.container(keyedBy: CodingKeys.self)
126+
defaultVal = container.tryNormalKeyDecode(type: String.self, key: .defaultVal) ?? "hello world"
127+
}
128+
129+
func encode(to encoder: Encoder) throws {
130+
var container = encoder.container(keyedBy: CodingKeys.self)
131+
try container.encode(defaultVal, forKey: .defaultVal)
132+
}
133+
}
134+
135+
extension KeyedDecodingContainer {
136+
func tryNormalKeyDecode<Value>(type _: Value.Type, key: K) -> Value? {
137+
let value = try? decodeIfPresent(AnyDecodable.self, forKey: key)?.value
138+
if let value = value {
139+
if let converted = value as? Value {
140+
return converted
141+
}
142+
if let _bridged = (Value.self as? _BuiltInBridgeType.Type)?._transform(from: value), let __bridged = _bridged as? Value {
143+
return __bridged
144+
}
145+
// 如果是非基本类型,那继续尝试Decode
146+
if let valueType = Value.self as? Decodable.Type {
147+
if let value = try? valueType.decode(from: self, forKey: key) as? Value {
148+
return value
149+
}
150+
}
151+
}
152+
return nil
153+
}
154+
}
155+
```
156+
157+
### 手动实现目标3:支持驼峰大小写自动互转
158+
```swift
159+
// 为了简化CodingKey,使用AnyCodingKey实现CodingKey
160+
public struct AnyCodingKey: CodingKey {
161+
public var stringValue: String
162+
public var intValue: Int?
163+
164+
public init?(stringValue: String) {
165+
self.stringValue = stringValue
166+
intValue = nil
167+
}
168+
169+
public init?(intValue: Int) {
170+
stringValue = "\(intValue)"
171+
self.intValue = intValue
172+
}
173+
174+
public init(index: Int) {
175+
stringValue = "\(index)"
176+
intValue = index
177+
}
178+
}
179+
180+
struct BasicModel: Codable {
181+
var defaultVal: String = "hello world"
182+
183+
init(from decoder: Decoder) throws {
184+
// CodingKeys 改为 AnyCodingKey
185+
let container = try decoder.container(keyedBy: AnyCodingKey.self)
186+
defaultVal = container.tryNormalKeyDecode(type: String.self, key: "defaultVal") ?? "hello world"
187+
}
188+
}
189+
190+
extension KeyedDecodingContainer where K == AnyCodingKey {
191+
func tryNormalKeyDecode<Value>(type: Value.Type, key: String) -> Value? {
192+
func _decode(key: String) -> Value? {
193+
let value = try? decodeIfPresent(AnyDecodable.self, forKey: key)?.value
194+
if let value = value {
195+
if let converted = value as? Value {
196+
return converted
197+
}
198+
if let _bridged = (Value.self as? _BuiltInBridgeType.Type)?._transform(from: value), let __bridged = _bridged as? Value {
199+
return __bridged
200+
}
201+
if let valueType = Value.self as? Decodable.Type {
202+
if let value = try? valueType.decode(from: self, forKey: key) as? Value {
203+
return value
204+
}
205+
}
206+
}
207+
return nil
208+
}
209+
210+
for newKey in [key, key.snakeCamelConvert()].compactMap({ $0 }) {
211+
if let value = _decode(key: newKey) {
212+
return value
213+
}
214+
}
215+
return nil
216+
}
217+
}
218+
219+
extension String {
220+
func snakeCamelConvert() -> String? {
221+
// 驼峰大小写互转
222+
...
223+
}
224+
}
225+
```
226+
227+
### 手动实现目标4:自定义解析Key和NestedKey
228+
[container.decode](https://github.com/winddpan/CodableWrapper/blob/swift5.9-macro/Sources/CodableWrapper/Decoder.swift)封装 [container.encode](https://github.com/winddpan/CodableWrapper/blob/swift5.9-macro/Sources/CodableWrapper/Encoder.swift)封装
229+
230+
```swift
231+
struct TestModel: Codable {
232+
// @CodingKey("u1", "u2", "u9")
233+
let userName: String = ""
234+
235+
// @CodingNestedKey("data.u1", "data.u2", "data.u9")
236+
let userName2: String = ""
237+
238+
init(from decoder: Decoder) throws {
239+
let container = try decoder.container(keyedBy: AnyCodingKey.self)
240+
userName = container.decode(type: String.self, keys: ["u1", "u2", "u9"], nestedKeys: []) ?? ""
241+
userName2 = container.decode(type: String.self, keys: [], nestedKeys: ["data.u1", "data.u2", "data.u9"]) ?? ""
242+
}
243+
244+
func encode(to encoder: Encoder) throws {
245+
var container = encoder.container(keyedBy: CodingKeys.self)
246+
try container.encode(value: self.userName, keys: ["u1", "u2", "u9"], nestedKeys: [])
247+
try container.encode(value: self.userName2, keys: [], nestedKeys: ["data.u1", "data.u2", "data.u9"])
248+
}
249+
}
250+
```
251+
252+
### 手动实现目标5:自定义解析规则Transformer
253+
```swift
254+
// 定义TransformType协议
255+
public protocol TransformType {
256+
associatedtype Object
257+
associatedtype JSON: Codable
258+
259+
func transformFromJSON(_ json: JSON?) -> Object
260+
func transformToJSON(_ object: Object) -> JSON?
261+
}
262+
```
263+
264+
```swift
265+
// 定义一个不遵循Codable的结构体
266+
struct DateWrapper {
267+
let timestamp: TimeInterval
268+
269+
var date: Date {
270+
Date(timeIntervalSince1970: timestamp)
271+
}
272+
273+
init(timestamp: TimeInterval) {
274+
self.timestamp = timestamp
275+
}
276+
277+
static var transformer = TransformOf<DateWrapper, TimeInterval>(fromJSON: { DateWrapper(timestamp: $0 ?? 0) }, toJSON: { $0.timestamp })
278+
}
279+
280+
struct DateModel: Codable {
281+
// @CodingTransformer(DateWrapper.transformer)
282+
var time: DateWrapper? = DateWrapper(timestamp: 0)
283+
284+
// @CodingTransformer(DateWrapper.transformer)
285+
var time1: DateWrapper = .init(timestamp: 0)
286+
287+
// @CodingTransformer(DateWrapper.transformer)
288+
var time2: DateWrapper?
289+
290+
// @CodingTransformer(DateWrapper.transformer)
291+
var time3: DateWrapper
292+
293+
init(from decoder: Decoder) throws {
294+
let container = try decoder.container(keyedBy: AnyCodingKey.self)
295+
let time = try? container.decode(type: TimeInterval.self, keys: ["time"], nestedKeys: [])
296+
self.time = DateWrapper.transformer.transformFromJSON(time) ?? DateWrapper(timestamp: 0)
297+
298+
let time1 = try? container.decode(type: TimeInterval.self, keys: ["time1"], nestedKeys: [])
299+
self.time1 = DateWrapper.transformer.transformFromJSON(time1) ?? .init(timestamp: 0)
300+
301+
let time2 = try? container.decode(type: TimeInterval.self, keys: ["time2"], nestedKeys: [])
302+
self.time2 = DateWrapper.transformer.transformFromJSON(time2)
303+
304+
let time3 = try? container.decode(type: TimeInterval.self, keys: ["time3"], nestedKeys: [])
305+
self.time3 = DateWrapper.transformer.transformFromJSON(time3)
306+
}
307+
308+
func encode(to encoder: Encoder) throws {
309+
let container = encoder.container(keyedBy: AnyCodingKey.self)
310+
if let time = self.time, let value = DateWrapper.transformer.transformToJSON(self.time) {
311+
try container.encode(value: value, keys: ["time"], nestedKeys: [])
312+
}
313+
if let value = DateWrapper.transformer.transformToJSON(self.time1) {
314+
try container.encode(value: value, keys: ["time1"], nestedKeys: [])
315+
}
316+
if let time2 = self.time2, let value = DateWrapper.transformer.transformToJSON(time2) {
317+
try container.encode(value: value, keys: ["time2"], nestedKeys: [])
318+
}
319+
if let value = DateWrapper.transformer.transformToJSON(time3) {
320+
try container.encode(value: value, keys: ["time3"], nestedKeys: [])
321+
}
322+
}
323+
}
324+
```
325+
326+
### 手动实现目标6:“不方便”的 `Codable Class` 子类
327+
```
328+
class ClassModel1: Codable {
329+
var val: String?
330+
}
331+
332+
class ClassSubmodel1: ClassModel1 {
333+
var subVal: String = "1_1"
334+
335+
required init(from decoder: Decoder) throws {
336+
let container = try decoder.container(keyedBy: AnyCodingKey.self)
337+
self.subVal = (try? container.decode(type: type(of: self.subVal), keys: ["subVal"], nestedKeys: [])) ?? ("1_1")
338+
try super.init(from: decoder)
339+
}
340+
341+
override func encode(to encoder: Encoder) throws {
342+
try super.encode(to: encoder)
343+
let container = encoder.container(keyedBy: AnyCodingKey.self)
344+
try container.encode(value: self.subVal, keys: ["subVal"], nestedKeys: [])
345+
}
346+
}
347+
```
348+
349+
下一篇文章将介绍Codable宏的开发和实现。
350+
351+
-----
352+
**文章目录**
353+
* [一、设计目标和手动实现这些目标特性](https://juejin.cn/post/7251501945272270908)
354+
* [二、Codable宏的开发和实现](https://juejin.cn/post/7252170693676499004)

0 commit comments

Comments
 (0)