HappyCodable
使用 SourceKittenFramework 通过自动创建 Codable 相关代码来让 Codable 更愉快。
什么是 Codable 的问题?
- 不支持对单个编码键的更改,一旦更改一个编码键,就需要设置所有编码键。
- 不支持忽略特定的编码键。
- 不支持为非 RawRepresentable 枚举自动生成代码,即使每个元素都是可编码的。
- 不支持多个键映射到一个属性。
- 调试困难。
- 当 JSON 数据中缺少相应键时,不自动使用定义中的默认值。
- 不支持 0/1 到 false/true
用法
- 构建 HappyCodable 命令行可执行文件
- 将可执行文件和 HappyCodable Common 源代码引入您的项目中
- 在
编译源代码
之前在构建阶段
中添加一个运行脚本,如下所示
${SRCROOT}/HappyCodableCommandLine ${SRCROOT}/Classes ${SRCROOT}/HappyCodable.generated.swift
- 将
HappyCodable
添加到一个结构体或类中
struct Person: HappyCodable {
var name1: String = "abc"
@Happy.codingKeys("🍉")
var numberOfTips2: String = "abc"
@Happy.codingKeys("234", "age", "abc") // the first key will be the coding key
var age: String = "abc"
@Happy.uncoding
var abc: String = "abc" // Build fail if coded, in this case, we can "uncoding" it.
}
然后 HappyCodableCommandLine 会自动创建代码
extension Person {
enum CodingKeys: String, CodingKey {
case name1
case numberOfTips2 = "🍉"
case age = "234"
}
mutating
func decode(happyFrom decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if Self.globalDecodeAllowOptional {
do { self.name1 = try decoder.decode(defaultValue: self.name1, verifyValue: self.name1, forKey: .name1, alterKeys: [], from: container) } catch { }
do { self.numberOfTips2 = try decoder.decode(defaultValue: self.numberOfTips2, verifyValue: self.numberOfTips2, forKey: .numberOfTips2, alterKeys: [], from: container) } catch { }
do { self.age = try decoder.decode(defaultValue: self.age, verifyValue: self.age, forKey: .age, alterKeys: ["age", "abc"], from: container) } catch { }
} else {
self.name1 = try decoder.decode(defaultValue: self.name1, verifyValue: self.name1, forKey: .name1, alterKeys: [], from: container)
self.numberOfTips2 = try decoder.decode(defaultValue: self.numberOfTips2, verifyValue: self.numberOfTips2, forKey: .numberOfTips2, alterKeys: [], from: container)
self.age = try decoder.decode(defaultValue: self.age, verifyValue: self.age, forKey: .age, alterKeys: ["age", "abc"], from: container)
}
}
func encode(happyTo encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if Self.globalEncodeSafely {
do { try container.encodeIfPresent(self.name1, forKey: .name1) } catch { }
do { try container.encodeIfPresent(self.numberOfTips2, forKey: .numberOfTips2) } catch { }
do { try container.encodeIfPresent(self.age, forKey: .age) } catch { }
} else {
try container.encode(self.name1, forKey: .name1)
try container.encode(self.numberOfTips2, forKey: .numberOfTips2)
try container.encode(self.age, forKey: .age)
}
}
}
也支持非 RawRepresentable 枚举
enum EnumTest: HappyCodableEnum {
case value(num: Int, name: String)
// case call(() -> Void) // Build fails if added, since (() -> Void) can't be codable
case name0(String)
case name1(String, last: String)
case name2(first: String, String)
case name3(_ first: String, _ last: String)
}
生成的代码
extension EnumTest {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let content = try container.decode([String: [String: String]].self)
let error = DecodingError.typeMismatch(EnumTest.self, DecodingError.Context(codingPath: [], debugDescription: ""))
guard let name = content.keys.first else {
throw error
}
let decoder = JSONDecoder()
switch name {
case ".value(num:name:)":
guard
let _0 = content[name]?["num"]?.data(using: .utf8),
let _1 = content[name]?["name"]?.data(using: .utf8)
else {
throw error
}
self = .value(
num: try decoder.decode((Int).self, from: _0),
name: try decoder.decode((String).self, from: _1)
)
case ".name0(_:)":
guard
let _0 = content[name]?["$0"]?.data(using: .utf8)
else {
throw error
}
self = .name0(
try decoder.decode((String).self, from: _0)
)
case ".name1(_:last:)":
guard
let _0 = content[name]?["$0"]?.data(using: .utf8),
let _1 = content[name]?["last"]?.data(using: .utf8)
else {
throw error
}
self = .name1(
try decoder.decode((String).self, from: _0),
last: try decoder.decode((String).self, from: _1)
)
case ".name2(first:_:)":
guard
let _0 = content[name]?["first"]?.data(using: .utf8),
let _1 = content[name]?["$1"]?.data(using: .utf8)
else {
throw error
}
self = .name2(
first: try decoder.decode((String).self, from: _0),
try decoder.decode((String).self, from: _1)
)
case ".name3(_:_:)":
guard
let _0 = content[name]?["first"]?.data(using: .utf8),
let _1 = content[name]?["last"]?.data(using: .utf8)
else {
throw error
}
self = .name3(
try decoder.decode((String).self, from: _0),
try decoder.decode((String).self, from: _1)
)
default:
throw error
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let encoder = JSONEncoder()
switch self {
case let .value(_0, _1):
try container.encode([
".value(num:name:)": [
"num": String(data: try encoder.encode(_0), encoding: .utf8),
"name": String(data: try encoder.encode(_1), encoding: .utf8)
]
])
case let .name0(_0):
try container.encode([
".name0(_:)": [
"$0": String(data: try encoder.encode(_0), encoding: .utf8)
]
])
case let .name1(_0, _1):
try container.encode([
".name1(_:last:)": [
"$0": String(data: try encoder.encode(_0), encoding: .utf8),
"last": String(data: try encoder.encode(_1), encoding: .utf8)
]
])
case let .name2(_0, _1):
try container.encode([
".name2(first:_:)": [
"first": String(data: try encoder.encode(_0), encoding: .utf8),
"$1": String(data: try encoder.encode(_1), encoding: .utf8)
]
])
case let .name3(_0, _1):
try container.encode([
".name3(_:_:)": [
"first": String(data: try encoder.encode(_0), encoding: .utf8),
"last": String(data: try encoder.encode(_1), encoding: .utf8)
]
])
}
}
}