diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..85fc6ea
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,134 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/osx,xcode,swift
+# Edit at https://www.toptal.com/developers/gitignore?templates=osx,xcode,swift
+
+### OSX ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Swift ###
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## User settings
+xcuserdata/
+
+## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
+*.xcscmblueprint
+*.xccheckout
+
+## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
+build/
+DerivedData/
+*.moved-aside
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+
+## Obj-C/Swift specific
+*.hmap
+
+## App packaging
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+# Packages/
+# Package.pins
+# Package.resolved
+# *.xcodeproj
+# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
+# hence it is not needed unless you have added a package configuration file to your project
+.swiftpm
+
+.build/
+
+# CocoaPods
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+Pods/
+# Add this line if you want to avoid checking in source code from the Xcode workspace
+# *.xcworkspace
+
+# Carthage
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build/
+
+# Accio dependency management
+Dependencies/
+.accio/
+
+# fastlane
+# It is recommended to not store the screenshots in the git repo.
+# Instead, use fastlane to re-generate the screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://docs.fastlane.tools/best-practices/source-control/#source-control
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots/**/*.png
+fastlane/test_output
+
+# Code Injection
+# After new code Injection tools there's a generated folder /iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/
+
+### Xcode ###
+# Xcode
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+
+
+
+## Gcc Patch
+/*.gcno
+
+### Xcode Patch ###
+*.xcodeproj/*
+!*.xcodeproj/project.pbxproj
+!*.xcodeproj/xcshareddata/
+!*.xcworkspace/contents.xcworkspacedata
+**/xcshareddata/WorkspaceSettings.xcsettings
+
+# End of https://www.toptal.com/developers/gitignore/api/osx,xcode,swift
diff --git a/0.x.md b/0.x.md
new file mode 100644
index 0000000..3964e15
--- /dev/null
+++ b/0.x.md
@@ -0,0 +1,257 @@
+---
+layout: default
+title: ExCodable 0.x
+description: KeyMapping Extensions for Swift Codable
+---
+
+ExCodable 0.x
+========
+
+[](https://github.com/ExCodable/ExCodable#readme)
+
+[](https://swift.org/)
+[](https://swift.org/package-manager/)
+[](#)
+
+[](https://github.com/ExCodable/ExCodable/actions/workflows/build-and-test.yml)
+[](https://github.com/ExCodable/ExCodable/releases)
+[](https://github.com/ExCodable/ExCodable/actions/workflows/deploy_to_cocoapods.yml)
+[](https://cocoapods.org/pods/ExCodable)
+
+[](https://github.com/ExCodable/ExCodable/blob/master/LICENSE)
+[](https://github.com/ExCodable/ExCodable/stargazers/)
+[](https://twitter.com/minglq)
+
+[En](https://github.com/ExCodable/ExCodable) \| 中文
+
+--------
+
+> - [**1.x**: Recently Released](./index.md)
+> - 0.x: DEPRECATED
+
+## [ExCodable](https://github.com/ExCodable/ExCodable) 是我在春节期间、带娃之余、用了几个晚上完成的一个 Swift 版的 JSON-Model 转换工具。
+
+别走!你可能会想“嗷,又一个轮子”,但是这个真的有点不一样:
+- `ExCodable` 是在 Swift `Codable` 基础上的扩展,**可以享受到诸多便利**,比如与 `NSCoding`、[Alamofire](https://github.com/Alamofire/Alamofire) 等无缝对接 —— `ExCodable` 没有做任何额外处理,躺赚那种;
+> [NSKeyedArchiver.encodeEncodable(_:forKey:)](https://developer.apple.com/documentation/foundation/nskeyedarchiver/2924373-encodeencodable)
+>
+> [NSKeyedUnarchiver.decodeTopLevelDecodable(_:forKey:)](https://developer.apple.com/documentation/foundation/nskeyedunarchiver/2924375-decodetopleveldecodable)
+>
+> [An Answer from Stack Overflow](https://stackoverflow.com/a/49952202/456536)
+>
+> [Response Decodable Handler from Alamofire](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-decodable-handler)
+>
+- 基于 `KeyPath` 实现 Key-Mapping,无需逐个属性 Encode/Decode;
+- 支持丰富的特性,差不多实现了 Objective-C 版的 [YYModel](https://github.com/ibireme/YYModel) 的所有特性;
+- 轻量,1 个文件、500 行代码。
+
+当然我不是一开始就决定要造轮子的。近期我们团队准备开始使用 Swift,节前开始寻找一些开源框架。网络请求用 [Alamofire](https://github.com/Alamofire/Alamofire)、自动布局用 [SnapKit](https://github.com/SnapKit/SnapKit),这都毫无悬念,但是 JSON-Model 转换并没有找到合适的。
+
+## Swift 内置的 `Codable` 可以满足刚需,但也有官方框架的通病 —— 繁琐;
+
+最基本的使用其实还是很便利的,**建议优先选用**:
+
+```swift
+struct TestAutoCodable: Codable {
+ private(set) var int: Int = 0 // `int` 一次
+ private(set) var string: String?
+}
+
+```
+
+但是,一旦不得不手动 Encode/Decode 就完蛋了,相同字段要出现 5 次,而且还要夹杂很多其它代码:
+
+```swift
+struct TestManualCodable: Codable {
+
+ private(set) var int: Int = 0 // `int` 一次
+ private(set) var string: String?
+
+ enum Keys: CodingKey {
+ case int, i // `int` 两次,这里省掉,在第三次、第五次的那里直接写字符串?不要!
+ case nested, string
+ }
+
+ init(from decoder: Decoder) throws {
+ if let container = try? decoder.container(keyedBy: Keys.self) {
+ if let int = (try? container.decodeIfPresent(Int.self, forKey: Keys.int)) // `int` 三次
+ ?? (try? container.decodeIfPresent(Int.self, forKey: Keys.i)) {
+ self.int = int // `int` 四次
+ }
+ if let nestedContainer = try? container.nestedContainer(keyedBy: Keys.self, forKey: Keys.nested),
+ let string = try? nestedContainer.decodeIfPresent(String.self, forKey: Keys.string) {
+ self.string = string
+ }
+ }
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: Keys.self)
+ try? container.encodeIfPresent(int, forKey: Keys.int) // `int` 五次
+ var nestedContainer = container.nestedContainer(keyedBy: Keys.self, forKey: Keys.nested)
+ try? nestedContainer.encodeIfPresent(string, forKey: Keys.string)
+ }
+}
+
+```
+
+## [Codextended](https://github.com/JohnSundell/Codextended) 对 `Codable` 做了大量的简化,但还是要逐个属性 Encode/Decode:
+
+```swift
+struct TestCodextended: Codable {
+
+ let int: Int // `int` 一次
+ let string: String?
+
+ /* Codextended 给的例子没有这个,而是在 `init` 和 `decode`
+ 方法里分别写两个 `"int"`、两个 `"string"`,但那是不对的,代码
+ 很多时可能会改了一个忘掉另一个 */
+ enum Keys: CodingKey {
+ case int, i // `int` 两次
+ case string
+ }
+
+ init(from decoder: Decoder) throws {
+ int = try decoder.decodeIfPresent(Keys.int) // `int` 三次
+ ?? try decoder.decodeIfPresent(Keys.i)
+ ?? 0
+ // !!!: Codextended 目前不支持 Nested-Keys
+ string = try decoder.decodeIfPresent(Keys.string)
+ }
+
+ func encode(to encoder: Encoder) throws {
+ try encoder.encode(int, for: "int") // `int` 四次
+ try encoder.encode(string, for: "string")
+ }
+}
+
+```
+
+## 另外 GitHub 上 Star 比较多的 [ObjectMapper](https://github.com/tristanhimmelman/ObjectMapper)、[HandyJSON](https://github.com/alibaba/HandyJSON)、[KakaJSON](https://github.com/kakaopensource/KakaJSON) 等:
+
+他们都各自构建了整套的序列化、反序列化机制,各有所长,但是相比 Objective-C 的 [YYModel](https://github.com/ibireme/YYModel) 多少都欠了点意思。与 `Codable` 不兼容、代码很复杂不说,甚至还有直接读写内存的 —— “依赖于从 Swift Runtime 源码中推断的内存规则,任何变动我们将随时跟进”,这就不大靠谱了,至少不够优雅。
+
+## ExCodable:
+
+调研一番之后倾向于用 Codextended,起初有考虑直接基于它做扩展来实现 Key-Mapping,但是受到限制较多,只能自己动手了。
+
+Codextended 最欠缺的是 Key-Mapping,经过各种摸索、尝试,确定 `KeyPath` 方式可行。解决掉关键问题后面就简单了,很快差不多实现了 YYModel 的所有特性;同时借鉴了 Codextended,重新写了关键部分的实现,有些调整、也有些舍弃。
+
+于是便有了 `ExCodable`:
+- 使用 `KeyPath` 映射到 Coding-Key,实现 Key-Mapping;
+- 支持多个候选 Coding-Key;
+- 支持 Coding-Key 嵌套;
+- 支持使用 Subscript 进行自定义 Encode/Decode;
+- 支持数据类型自动转换以及自定义转换;
+- 支持 `struct`、`class`、subclass;
+- 支持 JSON、PList 以及自定义 Encoder/Decoder,默认使用 JSON;
+- 使用类型推断,支持 `Data`、`String`、`Array`、`Object` 类型 JSON 数据;
+- 使用 `Optional` 类型取代抛出没什么用的 `error`,避免到处写 `try?`,有时还要套上括号 —— 现在也支持抛出异常了 [可选]。
+
+> 这里要多说两句:一般情况下抛出错误是有用的,但是在 JSON-Model 转换的场景略有不同。经常遇到的错误无非就是字段少了、类型错了。如果是关键数据有问题抛出错误也还好,但是有时不痛不痒的字段出错(这种更容易出错),导致整个解析都失败就不好了。确实这样可以及时发现返回结果中的问题,但是大家可能也知道经常有新“发现”是什么样的体验。老司机可以回忆一下 YYModel 出现之前的岁月。所以,永远不要相信关于 API 的任何承诺,不管它返回什么,App 不要动不动就死给人看,这会严重影响一个开发者的名声!可能有人会问,它真的给你返回一坨🍦怎么办?可以加个关键数据校验环节,只校验关键数据,而不是依赖异常。
+
+> 为了满足不同的编程习惯,`ExCodable` - 0.5.0 版本开始支持了个别/全部字段是否非空 - `nonnull`(Encode/Decode 时是否使用带有 `IfPresent` 的方法)、以及遇到异常时是否抛出 - `throws`。这两个参数都是 `Bool` 类型,组合使用可以产生不同的效果。比如某内嵌的对象指定某字段 `nonnull = true`、`throws = false`,遇到非空字段无法解析会导致该字段所属对象为 `nil`,但如果它外层对象没有指定该对象 `nonnull = true`,则会继续解析其它字段,而不是完全终止解析。
+
+## 上面场景,用 `ExCodable` 就简单多了:
+
+```swift
+struct TestExCodable: ExCodable, Equatable {
+
+ private(set) var int: Int = 0 // `int` 一次
+ private(set) var string: String?
+
+ static var keyMapping: [KeyMap] = [
+ KeyMap(\.int, to: "int", "i"), // `int` 两次
+ KeyMap(\.string, to: "nested.string")
+ ]
+
+ init(from decoder: Decoder) throws {
+ decode(from: decoder, with: Self.keyMapping)
+ }
+}
+
+```
+
+## `ExCodable` 用法解析:
+
+定义 `struct`,使用 `var` 声明变量、并设置默认值,可以使用 `private(set)` 来防止属性被外部修改;
+
+> `Optional` 类型不需要默认值;
+> 想用 `let` 也不是不可以,参考 [Usage](https://github.com/ExCodable/ExCodable#usage);
+
+```swift
+struct TestStruct: Equatable {
+ private(set) var int: Int = 0
+ private(set) var string: String?
+}
+
+```
+
+实现 `ExCodable` 协议,通过 `keyMapping` 设置 `KeyPath` 到 Coding-Key 的映射,`init` 和 `encode` 方法都只要一行代码;
+
+> 当 `encode` 方法只需这一行代码时它也是可以省略的,`ExCodable` 提供了默认实现。但是受 Swift 对初始化过程的严格限制,`init` 方法不能省略。
+
+```swift
+extension TestStruct: ExCodable {
+ static var keyMapping: [KeyMap] = [
+ KeyMap(\.int, to: "int", "i"),
+ KeyMap(\.string, to: "nested.string")
+ ]
+ init(from decoder: Decoder) throws {
+ decode(from: decoder, with: Self.keyMapping)
+ // 特殊逻辑同样可以在这里手动解决
+ // 比如 `string = (int == 0) ? decoder["a"] : decoder["b"]`
+ }
+ // func encode(to encoder: Encoder) throws {
+ // encode(to: encoder, with: Self.keyMapping)
+ // // 特殊逻辑同样可以在这里手动解决
+ // // 比如 `encoder["string"] = (string == "") ? nil : string`
+ // }
+}
+
+```
+
+序列化、反序列化也很方便;
+
+```swift
+let test = TestStruct(int: 100, string: "Continue")
+let data = test.encoded() as Data? // Model encode to JSON
+
+let copy1 = data?.decoded() as TestStruct? // decode JSON to Model
+let copy2 = TestStruct.decoded(from: data) // or Model decode from JSON
+
+XCTAssertEqual(copy1, test)
+XCTAssertEqual(copy2, test)
+
+```
+
+更多示例可参考 [Usage](https://github.com/ExCodable/ExCodable#usage) 以及单元测试代码。
+
+## 将下面代码片段添加到 Xcode,只要记住 `ExCodable` 就可以了:
+
+> Language: Swift
+> Platform: All
+> Completion: ExCodable
+> Availability: Top Level
+
+```swift
+<#extension/struct/class#> <#Type#>: ExCodable {
+ static var <#keyMapping#>: [KeyMap<<#SelfType#>>] = [
+ KeyMap(\.<#property#>, to: <#"key"#>),
+ <#...#>
+ ]
+ init(from decoder: Decoder) throws {
+ decode<#Reference#>(from: decoder, with: Self.<#keyMapping#>)
+ }
+ func encode(to encoder: Encoder) throws {
+ encode(to: encoder, with: Self.<#keyMapping#>)
+ }
+}
+
+```
+
+## 感谢
+
+在此,要特别感谢 John Sundell 的 [Codextended](https://github.com/JohnSundell/Codextended) 的非凡创意、以及 ibireme 的 [YYModel](https://github.com/ibireme/YYModel) 的丰富特性,他们给了我极大的启发。
+
+`ExCodable` 还是一个崭新的框架,使用中遇到任何问题欢迎反馈:[i+ExCodable@iwill.im](mailto:i+ExCodable@iwill.im)。
diff --git a/CNAME b/CNAME
new file mode 100644
index 0000000..5e0cc3d
--- /dev/null
+++ b/CNAME
@@ -0,0 +1 @@
+excodable.iwill.im
\ No newline at end of file
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..c419263
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-cayman
\ No newline at end of file
diff --git a/index.md b/index.md
new file mode 100644
index 0000000..3783751
--- /dev/null
+++ b/index.md
@@ -0,0 +1,431 @@
+---
+layout: default
+title: ExCodable
+---
+
+ExCodable 1.0
+========
+
+[](https://github.com/ExCodable/ExCodable#readme)
+
+[](https://swift.org/)
+[](https://swift.org/package-manager/)
+[](#)
+
+[](https://github.com/ExCodable/ExCodable/actions/workflows/build-and-test.yml)
+[](https://github.com/ExCodable/ExCodable/releases)
+[](https://github.com/ExCodable/ExCodable/actions/workflows/deploy_to_cocoapods.yml)
+[](https://cocoapods.org/pods/ExCodable)
+
+[](https://github.com/ExCodable/ExCodable/blob/master/LICENSE)
+[](https://github.com/ExCodable/ExCodable/stargazers/)
+[](https://twitter.com/minglq)
+
+[En](https://github.com/ExCodable/ExCodable) \| 中文
+
+--------
+
+ExCodable 是一个 Swift 版 JSON-Model 转换工具,现在迎来重要升级,发布 1.0 版本。
+
+「若非必要,勿造轮子」。但显然,我又造了一个,所以它一定是必要的:
+
+- Swift 内置的 `Codable` 能用,但也有官方框架的通病 —— 繁琐
+- [Codextended](https://github.com/JohnSundell/Codextended) 想法特别好,对 `Codable` 做了大量的简化,但还是要逐个属性 encode/decode
+- 其它 star 较多的 [ObjectMapper](https://github.com/tristanhimmelman/ObjectMapper)、[HandyJSON](https://github.com/alibaba/HandyJSON)、[KakaJSON](https://github.com/kakaopensource/KakaJSON) 等等(不知道最近两年有没有新的),都不兼容 `Codable`,语法奇怪、繁琐,甚至还有读写内存的,说实话 —— 丑得要死!
+
+ObjC 时代,最好的 JSON-Model 转换非 [YYModel](https://github.com/ibireme/YYModel) 莫属,可惜没有 Swift 版,所以 [自己写一个吧](./0.x.md/#excodable-是我在春节期间带娃之余用了几个晚上完成的一个-swift-版的-json-model-转换工具)。
+
+## 主要特性
+
+```swift
+struct TestExCodable: ExAutoCodable {
+ @ExCodable
+ var int: Int = 0
+ @ExCodable("string", "s", "nested.nested.string")
+ var string: String? = nil
+}
+
+```
+
+上面代码虽少,但足可以体现 ExCodable 的强大。
+
+主要特性:
+
+- ExCodable 是对 Swift 内置的 `Codable` 的扩展,因此可以享受到诸多便利,比如与 `NSCoding`、[SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)、[GenericJSON](https://github.com/iwill/generic-json-swift)、[Alamofire](https://github.com/Alamofire/Alamofire) 等都能无缝对接
+- 在属性上添加注解(`@propertyWrapper`)绑定 JSON key ,JSON key 与属性同名时,可以简写为 `@ExCodable` —— 目前位置最简单、直观的方式
+- 支持多个候选 JSON key,依次解析
+- 使用 `.` 拼接多层嵌套的 JSON key
+- 需要手动 encode/decode 时,可以使用便捷的 subscript 语法
+- 支持常见的数据类型转换,以及灵活的自定义转换
+- 灵活的异常处理,出错时默认返回 `nil`,也支持设置抛出 `error`
+- 支持 `struct`、`enum`、`class` 以及子类
+- 支持 `Data`、`String`、`Array`、`Dictionary` 等 JSON 格式
+- 支持类型推断
+- 声明实现 `ExAutoCodable` 协议,即可自动获得 `Codable` 方法的默认实现,无需对属性逐个 encode/decode
+- 支持 JSON、PList 以及自定义 encoder/decoder,默认使用 JSON
+- 非常轻量
+
+## 使用方法
+
+### 1、ExCodable
+
+目前,使用 ExCodable 定义属性需要使用 `var`(可以使用 `private(set)` 避免属性被意外修改),并且要提供默认值。
+
+```swift
+struct TestStruct: ExAutoCodable {
+ @ExCodable private(set)
+ var int: Int = 0
+ @ExCodable("string") private(set)
+ var string: String? = nil
+}
+
+```
+
+### 2、多个候选 JSON key
+
+```swift
+struct TestAlternativeKeys: ExAutoCodable {
+ @ExCodable("string", "str", "s") private(set)
+ var string: String! = nil
+}
+
+```
+
+### 3、多层嵌套的 JSON key
+
+```swift
+struct TestNestedKeys: ExAutoCodable {
+ @ExCodable("nested.nested.string") private(set)
+ var string: String! = nil
+}
+
+```
+
+### 4、`RawRepresentable` 类型的 `enum`
+
+```swift
+enum TestEnum: Int, Codable {
+ case zero = 0, one = 1
+}
+
+struct TestStructWithEnum: ExAutoCodable {
+ @ExCodable private(set)
+ var `enum` = TestEnum.zero
+}
+
+```
+
+非 `RawRepresentable` 类型的 `enum` 需要自定义的 encode/decode。
+
+### 5、自定义 Encode/Decode
+
+`@ExCodable` 支持自定义 `encode` 和 `decode`,并且 `encoder`、`decoder` 支持使用 subscript 方式访问。
+
+```swift
+struct TestManualEncodeDecode: ExAutoCodable {
+ @ExCodable(encode: { encoder, value in
+ encoder["int"] = value <= 0 ? 0 : value
+ }, decode: { decoder in
+ if let int: Int = decoder["int"], int > 0 {
+ return int
+ }
+ return 0
+ }) private(set)
+ var int: Int = 0
+}
+
+```
+
+### 6、自动类型转换
+
+ExCodable 支持灵活的类型转换,兼容多层嵌套的 `Optional` 类型,例如 `Int???`。并且这些转换同样适用于 `RawRepresentable` 类型的属性,自动转换到 `RawValue` 类型并调用 `init(rawValue:)`。
+
+A、内置支持的类型转换:
+
+- boolean:
+ - `Bool`
+ - `Int`, `Int8`, `Int16`, `Int32`, `Int64`
+ - `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`
+ - `String`
+- integer:
+ - `Bool`
+ - `Int`, `Int8`, `Int16`, `Int32`, `Int64`
+ - `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`
+ - `Double`, `Float`
+ - `String`
+- float:
+ - `Int`, `Int8`, `Int16`, `Int32`, `Int64`
+ - `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`
+ - `Double`, `Float`
+ - `String`
+- string:
+ - `Bool`
+ - `Int`, `Int8`, `Int16`, `Int32`, `Int64`
+ - `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`
+ - `Double`, `Float`
+ - `String`
+
+B、单个属性的自定义类型转换:
+
+```swift
+struct TestCustomEncodeDecode: ExAutoCodable {
+ @ExCodable(decode: { decoder in
+ if let string: String = decoder["string"] {
+ return string.count
+ }
+ return 0
+ }) private(set)
+ var int: Int = 0
+}
+
+```
+
+C、model 中的自定义类型转换:
+
+```swift
+struct TestCustomTypeConverter: ExAutoCodable {
+ @ExCodable private(set)
+ var doubleFromBool: Double? = nil
+ @ExCodable private(set)
+ var floatFromBool: Double? = nil
+}
+
+extension TestCustomTypeConverter: ExCodableDecodingTypeConverter {
+
+ public static func decode(_ container: KeyedDecodingContainer, codingKey: K, as type: T.Type) -> T? {
+
+ // for nested optionals, e.g. `var int: Int??? = nil`
+ let wrappedType = T?.wrappedType
+
+ // decode Double from Bool
+ if type is Double.Type || wrappedType is Double.Type {
+ if let bool = try? container.decodeIfPresent(Bool.self, forKey: codingKey) {
+ return (bool ? 1.0 : 0.0) as? T
+ }
+ }
+ // decode Float from Bool
+ else if type is Float.Type || wrappedType is Float.Type {
+ if let bool = try? container.decodeIfPresent(Bool.self, forKey: codingKey) {
+ return (bool ? 1.0 : 0.0) as? T
+ }
+ }
+
+ return nil
+ }
+}
+
+```
+
+D、全局的自定义类型转换:
+
+```swift
+struct TestCustomGlobalTypeConverter: ExAutoCodable, Equatable {
+ @ExCodable private(set)
+ var boolFromDouble: Bool? = nil
+}
+
+extension ExCodableGlobalDecodingTypeConverter: ExCodableDecodingTypeConverter {
+
+ public static func decode(_ container: KeyedDecodingContainer<_K>, codingKey: _K, as type: T.Type) -> T? {
+
+ // for nested optionals, e.g. `var int: Int??? = nil`
+ let wrappedType = T?.wrappedType
+
+ // decode Bool from Double
+ if type is Bool.Type || wrappedType is Bool.Type {
+ if let double = try? container.decodeIfPresent(Double.self, forKey: codingKey) {
+ return (double != 0) as? T
+ }
+ }
+
+ return nil
+ }
+}
+
+```
+
+### 7、使用 Subscript 手动 Encode/Decode
+
+对于一个未使用 `ExCodable` 的类型:
+
+```swift
+struct TestManualEncodingDecoding {
+ let int: Int
+ let string: String
+}
+
+```
+
+使用 subscript 手动 encode/decode,要比 Swift 原生语法简单得多:
+
+```swift
+extension TestManualEncodingDecoding: Codable {
+
+ enum Keys: CodingKey {
+ case int, string
+ }
+
+ init(from decoder: Decoder) throws {
+ int = decoder[Keys.int] ?? 0
+ string = decoder[Keys.string] ?? ""
+ }
+ func encode(to encoder: Encoder) throws {
+ encoder[Keys.int] = int
+ encoder[Keys.string] = string
+ }
+}
+
+```
+
+### 8、更灵活的异常处理
+
+ExCodable 默认忽略 JSON-Model 转换时遇到的 `EncodingError.invalidValue`、`DecodingError.keyNotFound`、`DecodingError.valueNotFound` 和 `DecodingError.typeMismatch` 等错误,出错的属性不处理 —— encode 时跳过、decode 时保持默认值。只有 JSON 数据本身有问题时才会抛出错误。
+
+ExCodable 也支持出错时终止转换,抛出错误:
+
+- 设置 `nonnull: true` 允许抛出 `EncodingError.invalidValue`、`DecodingError.keyNotFound` 和 `DecodingError.valueNotFound`
+- 设置 `throws: true` 允许抛出 `DecodingError.typeMismatch`
+
+```swift
+struct TestNonnullAndThrows: ExAutoCodable {
+ @ExCodable(nonnull: true, throws: true) private(set)
+ var int: Int! = 0
+}
+
+```
+
+### 9、`class` 以及子类
+
+```swift
+class TestClass: ExAutoCodable {
+
+ @ExCodable private(set)
+ var int: Int = 0
+ @ExCodable private(set)
+ var string: String? = nil
+
+ required init() {}
+ init(int: Int, string: String?) {
+ (self.int, self.string) = (int, string)
+ }
+}
+
+```
+
+```swift
+class TestSubclass: TestClass {
+
+ @ExCodable private(set)
+ var bool: Bool = false
+
+ required init() { super.init() }
+ required init(int: Int, string: String, bool: Bool) {
+ self.bool = bool
+ super.init(int: int, string: string)
+ }
+}
+
+```
+
+### 10、类型推断
+
+```swift
+struct TestStruct: ExAutoCodable, Equatable {
+ @ExCodable private(set)
+ var int: Int = 0
+ @ExCodable private(set)
+ var string: String? = nil
+}
+
+// 正常的转换
+let json = Data(#"{"int":200,"string":"OK"}"#.utf8)
+let model = try? TestStruct.decoded(from: json)
+
+// 类型推断
+let dict = try? model.encoded() as [String: Any]
+let copy = try? dict.decoded() as TestStruct
+
+```
+
+> 更多用法参考代码中的单元测试。
+
+## 安装
+
+Swift Package Manager:
+
+```swift
+.package(url: "https://github.com/ExCodable/ExCodable", from: "1.0.0")
+```
+
+CocoaPods:
+
+```ruby
+pod 'ExCodable', '~> 1.0.0'
+```
+
+## 升级
+
+如果你用过 0.x 版本,感谢支持!但是时候升级了,ExCodable 依然保留了旧的 API,从而降低升级的难度。
+
+首先,升级到 1.0 之后可以继续使用废弃的 API —— 快速、工作量小:
+
+- 全局搜索 `ExCodable`,替换成 `ExCodableDEPRECATED`
+- 如果你实现了 `KeyedDecodingContainerCustomTypeConversion` 的 `decodeForTypeConversion(_:codingKey:as:)` 方法,在前面添加一个 `static`
+
+```swift
+struct TestExCodable {
+ private(set) var int: Int = 0
+ private(set) var string: String?
+}
+
+extension TestExCodable: ExCodableDEPRECATED {
+ static let keyMapping: [KeyMap] = [
+ KeyMap(\.int, to: "int"),
+ KeyMap(\.string, to: "nested.nested.string", "string", "str", "s")
+ ]
+ init(from decoder: Decoder) throws {
+ try decode(from: decoder, with: Self.keyMapping)
+ }
+}
+
+```
+
+然后逐步升级到新的语法:
+
+- 使用 `ExAutoCodable` 协议替代 `ExCodable`
+- 删除 `init(from decoder: Decoder) throws` 方法
+- 删除 `keyMapping` 静态属性
+- 改用 `@ExCodable("", "", ...)` 绑定 JSON key
+- 具体参考上面 [使用方法](#使用方法)
+
+```swift
+struct TestExCodable: ExAutoCodable {
+ @ExCodable private(set)
+ var int: Int = 0
+ @ExCodable("nested.nested.string", "string", "str", "s") private(set)
+ var string: String?
+}
+
+```
+
+## 未来
+
+Swift 5.9 发布时引入了 [Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/),很快我就看到了基于它实现的 [MetaCodable](https://github.com/SwiftyLab/MetaCodable),这是目前最科学的「实现方式」。这让我一度想放弃维护 ExCodable,但我还是更喜欢 ExCodable 灵活的[「使用方式」](#使用方法)。
+
+计划未来也使用 Macros 实现重写 ExCodable,在保持目前良好特性的同时,突破 Swift 语法对目前方案的种种限制,敬请期待 —— 不确定多久 🫣
+
+## 星星
+
+如果你喜欢 ExCodable,欢迎 [给个星星](https://github.com/ExCodable/ExCodable#repository-container-header) ⭐️ 🤩
+
+## 致敬
+
+在此,再次,致敬 John Sundell 和 ibireme,[Codextended](https://github.com/JohnSundell/Codextended) 的非凡创意和 [YYModel](https://github.com/ibireme/YYModel) 的丰富特性给了我的极大的启发!
+
+## 关于
+
+我是 [Míng](https://github.com/iwill),使用中遇到任何问题,欢迎 [反馈](https://github.com/ExCodable/ExCodable/issues) / [i+ExCodable@iwill.im](mailto:i+ExCodable@iwill.im)。
+
+## 开源
+
+[代码](https://github.com/ExCodable/ExCodable) 在 [MIT](https://github.com/ExCodable/ExCodable/blob/master/LICENSE) 协议下开源。