Kodable
是通过属性包装器对 Codable
功能的扩展。主要目标是在添加有用功能的同时,消除样板代码。
特性
init(from decoder: Decoder)
或 CodingKeys
.
符号访问嵌套值String
和 Bool
作为后备DecodingError
更好的可读性如果您直接在 Package 中工作,请将 Kodable 添加到您的 Package.swift 文件中
dependencies: [
.package(url: "https://github.com/JARMourato/Kodable.git", .upToNextMajor(from: "1.1.0")),
]
如果在 Xcode 项目中工作,请选择 File->Swift Packages->Add Package Dependency...
并搜索包名称:Kodable
或 git url
https://github.com/JARMourato/Kodable.git
只需让您的类型符合 Kodable
,您就可以访问 Coding
带来的所有功能。您可以将 Codable
值与 Coding
属性混合搭配。
声明你的模型
struct User: Kodable {
var identifier: String = ""
var social: String?
@Coding("first_name") var firstName: String
@Coding(default: "+1 123456789") var phone: String
@Coding("address.zipCode") var zipCode: Int
}
// Instead of
struct CodableUser: Codable {
enum Keys: String, CodingKey {
case identifier, social, firstName = "first_name", phone, address
}
enum NestedKeys: String, CodingKey {
case zipCode
}
var identifier: String = ""
var social: String?
var firstName: String
var phone: String
var zipCode: Int
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
identifier = try container.decode(String.self, forKey: .identifier)
social = try container.decodeIfPresent(String.self, forKey: .social)
firstName = try container.decode(String.self, forKey: .firstName)
phone = try container.decodeIfPresent(String.self, forKey: .phone) ?? "+1 123456789"
let addressContainer = try container.nestedContainer(keyedBy: NestedKeys.self, forKey: .address)
zipCode = try addressContainer.decode(Int.self, forKey: .zip)
}
}
然后
let json = """
{
identifier: "1",
"social": 987654321,
"first_name": John,
"address": {
"zipCode": 94040,
}
}
""".data(using: .utf8)!
let result = try JSONDecoder().decode(User.self, from: json)
// or using the provided syntactic sugar
let user = try User.decode(from: json)
此包装器允许按属性策略解码日期。默认情况下,CodableDate
使用 iso8601
策略。内置策略有:iso8601
、iso8601WithMillisecondPrecision
、rfc2822
、rfc3339
和 timestamp
。还可以通过将有效的字符串格式提供给选项 .format()
来使用自定义格式。
struct Dates: Kodable {
@CodableDate var iso8601: Date
@CodableDate(.format("y-MM-dd"), .key("simple_date")) var simpleDate: Date
@CodableDate(.rfc2822, .key("rfc2822")) var rfc2822Date: Date
@CodableDate(.rfc3339, .key("rfc3339")) var rfc3339Date: Date
@CodableDate(.timestamp, .key("timestamp")) var timestamp: Date
}
let json = """
{
"iso8601": "1996-12-19T16:39:57-08:00",
"simple_date": "2001-01-01",
"rfc2822": "Thu, 19 Dec 1996 16:39:57 GMT",
"rfc3339": "1996-12-19T16:39:57-08:00",
"timestamp": 978307200.0,
}
""".data(using: .utf8)!
let dates = Dates.decode(from: json)
print(dates.iso8601.description) // Prints "1996-12-20 00:39:57 +0000"
print(dates.simpleDate.description) // Prints "2001-01-01 00:00:00 +0000"
print(dates.rfc2822Date.description) // Prints "1996-12-19 16:39:57 +0000"
print(dates.rfc3339Date.description) // Prints "1996-12-20 00:39:57 +0000"
print(dates.timestamp.description) // Prints "2001-01-01 00:00:00 +0000"
请注意,目前尚不支持精度高于毫秒的 ISO8601 日期(例如,微秒或纳秒),因为 Apple 尚未正式原生支持此类精度。如果您觉得需要这些或任何其他自定义日期格式化程序,您可以实现自己的 DateConvertible
并使用 .custom(dateConvertible)
DateCodingStrategy。如果您认为您的用例应该加入官方库,我们随时欢迎 PR!
对于 Array
、Bool
和 String
类型,引入了一些有损解码。以后可以添加更多类型,但目前这些足以满足我的个人使用。要为特定属性禁用此行为,以防您希望在类型不正确时解码失败,只需将 enforceTypeDecoding
选项提供给 Coding
属性包装器。
Array
上的有损解码是通过尝试以非有损方式从 Array.Element
类型解码每个元素(即使它们是 Bool
或 String
)来完成的,并且会忽略解码失败的值。
struct LossyArray: Kodable {
@Coding("failable_array", .lossy) var array: [String]
}
let json = """
{
"failable_array": [ "1", 1.5, "2", true, "3", null, 4 ]
}
""".data(using: .utf8)!
let lossy = try LossyArray.decode(from: json)
print(lossy.array) // Prints [ "1", "2", "3" ]
如果 Bool
失败,尝试从 Int
或 String
解码 Bool
struct Fail: Kodable {
@Coding("string_bool", .enforceTypeDecoding) var notBool: Bool
}
struct Success: Kodable {
@Coding("string_bool") var stringBool: Bool
@Coding("int_bool") var intBool: Bool
}
let json = """
{
"string_bool": "false",
"int_bool": 1,
}
""".data(using: .utf8)!
let success = try Success.decode(from: json)
print(success.stringBool) // prints false
print(success.intBool) // prints true
let fail = try Fail.decode(from: json) // Throws KodableError.invalidValueForPropertyWithKey("string_bool")
如果 String
失败,尝试从 Double
或 Int
解码 String
struct Amounts: Kodable {
@Coding("double") var double: String
@Coding("int") var integer: String
@Coding var string: String
}
let json = """
{
"double": 629.9,
"int": 1563,
"string": "999.9"
}
""".data(using: .utf8)!
let amounts = try Amounts.decode(from: json)
print(amounts.double) // prints "629.9"
print(amounts.integer) // prints "1563"
print(amounts.string) // prints "999.9"
您可以提供带有重写闭包的 KodableModifier.custom
修饰符,以便您可以在将解码后的值分配给属性之前对其进行修改。
struct Project: Kodable {
@Coding(.modifier(Project.trimmed)) var title: String
static var trimmed: KodableModifier<String> {
KodableModifier { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
}
}
let json = #"{ "title": " A New Project " }"#.data(using: .utf8)!
let project = try Project.decode(from: json)
print(project.title) // Prints "A New Project"
已经提供了一些内置的修饰符
String
trimmed
:将 trimmingCharacters(in: .whitespacesAndNewlines)
应用于解码后的值String?
trimmed
:将 trimmingCharacters(in: .whitespacesAndNewlines)
应用于解码后的值trimmedNifIfEmpty
:将 trimmingCharacters(in: .whitespacesAndNewlines)
应用于解码后的值,如果为空则返回 nif排序 当类型符合 Comparable
协议时
ascending
或 descending
:使用类型的底层比较函数,按升序(或降序)对数组的元素进行排序。当类型不符合 Comparable
协议,但其一个属性符合时
ascending(by: KeyPath)
或 descending(by: KeyPath)
:基于传递的 KeyPath 属性,按升序(或降序)对数组的元素进行排序。如果没有完全符合 Comparable
,您可以求助于基本的排序功能
sorted(using: Comparator)
:使用 Comparator 闭包对数组的元素进行排序,该闭包确定项目是否按升序排列。用 Swifty 的话说:func < (lhs: Value.Element, rhs: Value.Element) -> Bool
Comparable
clamping(to:)
:将值限制在范围内。range()
:将值约束在提供的范围内。max()
:将值约束为最大值。min()
:将值约束为最小值。您可以提供带有验证闭包的 KodableModifier.validation
修饰符,您可以在其中验证该值是否有效。
struct Image: Kodable {
@Coding(.validation({ $0 > 500 })) var width: Int
}
let json = #{ "width": 400 }#.data(using: .utf8)!
let image = try Image.decode(from: json)
// Throws KodableError.validationFailed(property: "width", parsedValue: 400)
Kodable
基于一个名为 KodableTransform
的协议构建
public protocol KodableTransform {
associatedtype From: Codable
associatedtype To
func transformFromJSON(value: From) throws -> To
func transformToJSON(value: To) throws -> From
init()
}
如果要添加自己的自定义行为,您可以创建一个符合 KodableTransform
协议的类型。
struct URLTransformer: KodableTransform {
enum Error: Swift.Error {
case failedToCreateURL
}
func transformFromJSON(value: String) throws -> URL {
guard let url = URL(string: value) else { throw Error.failedToCreateURL }
return url
}
func transformToJSON(value: URL) throws -> String {
value.absoluteString
}
}
然后使用 KodableTrasformable
属性包装器,所有其他包装器都基于此
typealias CodingURL = KodableTransformable<URLTransformer>
然后瞧!
struct Test: Kodable {
@CodingURL("html_url") var url: URL
}
默认情况下,可选值不会被编码,因此
struct User: Kodable {
@Coding var firstName: String
@Coding var lastName: String?
}
let user = User()
user.firstName = "João"
编码后将输出
{
"firstName": "João"
}
但是,如果您想显式编码 null 值,那么您可以添加 encodeAsNullIfNil
选项
struct User: Kodable {
@Coding var firstName: String
@Coding(.encodeAsNullIfNil) var lastName: String?
}
let user = User()
user.firstName = "João"
然后它将输出
{
"firstName": "João",
"lastName": null
}
在开发过程中,了解接收到的 JSON 可能很有用,这样我们才能确定所选的选项会导致正确的解码。有很多方法可以做到这一点,但是,为了简单起见,Kodable 提供了一种打印接收到的 JSON 值的简单方法。
让我们以以下 JSON 和 Kodable 模型为例
{
"identifier": "1",
"social": 987654321,
"first_name": "John",
"address": {
"zipCode": 94040,
"state": "CA"
},
"aliases": [ "Jay", "Doe" ]
}
struct Address: Codable {
let zipCode: Int
let state: String
}
struct User: Kodable {
var identifier: String = ""
var social: String?
@Coding("first_name") var firstName: String
@Coding(default: "+1 123456789") var phone: String
@Coding var address: Address
}
Kodable 提供了 2 种调试将用于解码 User
模型的 JSON 的方法。第一种是检查模型的整个 JSON 值。为此,请使模型符合 DebugJSON
协议
struct User: Kodable, DebugJSON {
/.../
}
每当解码 User
模型的实例时,您都会在控制台中收到以下消息
Decoded JSON for type User:
{
"identifier": "1",
"social": 987654321,
"first_name": "John",
"address": {
"zipCode": 94040,
"state": "CA"
},
"aliases": [ "Jay", "Doe" ]
}
但是,有时该模型可能非常广泛,您只对特定的嵌套模型感兴趣。在这种情况下,有第二个选项,即仅使用选项 .debugJSON
标记您想要的属性
struct User: Kodable {
var identifier: String = ""
var social: String?
@Coding("first_name") var firstName: String
@Coding(default: "+1 123456789") var phone: String
@Coding(.debugJSON) var address: Address
}
在这种情况下,对于解码的 User
模型的每个实例,您都会在控制台中收到以下消息
Decoded JSON for the address property of type User:
{
"zipCode": 94040,
"state": "CA"
}
如果您觉得缺少某些内容或想要添加任何新功能,请打开一个 issue 请求它和/或提交一个带有通过测试的 pull 请求 🙌
MIT
更好的解码错误消息 - 通过 @nunogoncalves
João (@_JARMourato)