Alter

Alter 是一个框架,可以更轻松地映射 Codable 属性和键。

使用 Alter,您无需创建 CodingKey 来手动映射键和属性。

Alter 使用 propertyWrapper 和反射来实现键-属性映射。

codebeat badge build test SwiftPM Compatible Version License Platform

要求

安装

Cocoapods

Alter 可以通过 CocoaPods 获得。 要安装它,只需将以下行添加到您的 Podfile 中

pod 'Alter', '~> 1.2.9'

XCode 的 Swift Package Manager

Package.swift 的 Swift Package Manager

Package.swift 中将其添加为您的目标依赖项

dependencies: [
    .package(url: "https://github.com/hainayanda/Alter.git", .upToNextMajor(from: "1.2.9"))
]

在您的目标中使用它作为 Alter

 .target(
    name: "MyModule",
    dependencies: ["Alter"]
)

作者

Nayanda Haberty, hainayanda@outlook.com

许可证

Alter 在 MIT 许可证下可用。 有关更多信息,请参阅 LICENSE 文件。

用法

基本用法

例如,如果您需要将 User JSON 映射到 Swift 对象,您只需要创建 User 类/结构体并实现 Alterable protocol,然后使用 @Mapped 属性标记所有需要映射到 JSON 的属性。

struct User: Codable, Alterable {
    
    @Mapped
    var name: String = ""
    
    @Mapped
    var userName: String = ""
    
    @Mapped
    var age: Int = 0
}

或者直接使用 AlterCodable,它是 Alterable & Codabletypealias

struct User: AlterCodable {
    
    @Mapped
    var name: String = ""
    
    @Mapped
    var userName: String = ""
    
    @Mapped
    var age: Int = 0
}

然后您可以像这样将 JSON 解析为 User Swift 对象或反之亦然

let user: User = getUserFromSomewhere()

//to JSON
let jsonObject: [String: Any] = try! user.toJSON()
let jsonString: String = try! user.toJSONString()
let jsonData: Data = try! user.toJSONData()

//from JSON
let userFromJSON: User = try! .from(json: jsonObject)
let userFromString: User = try! .from(jsonString: jsonString)
let userFromData: User = try! .from(jsonData: jsonData)

实际上,Alterable 只是一个简单的协议,如果与 Codable 配对,它将发挥全部功能。 从 Codable 唯一可扩展的功能是 Alterable 将使用反射来获取所有 Mapped 属性,并使用它来进行双向映射。

public protocol Alterable

由于 AlterCodable 符合 Codable,您可以始终使用 codable decoder 进行解码,或者使用 codable encoder 进行编码,就像 Codable 一样

let user: User = getUserFromSomewhere()
let propertyListData = try! PropertyListEncoder().encode(user)
let decodedPropertyList = try! PropertyListDecoder().decode(User.self, from: propertyListData)

let jsonData = try! JSONEncoder().encode(user)
let decodedJsonData = try! JSONDecoder().decode(User.self, from: jsonData)

Alterable 的真正强大之处在于映射功能,它消除了手动进行键映射时枚举 CodingKey 的要求。 如果解码数据的属性名称与 Swift 对象中的属性名称不同,则可以在属性传递该属性的名称,而不是创建 CodingKey 枚举。 然后,这些属性将使用这些键进行映射。

struct User: AlterCodable {
    
    @Mapped(key: "full_name")
    var fullName: String = ""
    
    @Mapped(key: "user_name")
    var userName: String = ""
    
    @Mapped
    var age: Int = 0
}

您始终可以通过实现 init(from:) throwsfunc encode(to:) throws 来手动进行解码和编码。 Alterable 有一些扩展来帮助您手动实现解码和编码

struct User: AlterCodable {
    
    @Mapped(key: "full_name")
    var fullName: String = ""
    
    @Mapped(key: "user_name")
    var userName: String = ""
    
    @Mapped
    var age: Int = 0
    
    var image: UIImage? = nil
    
    required init() {}
    
    init(from decoder: Decoder) throws {
        self.init()
        // this will automatically decode all Mapped properties and return container which you could use to decode property that not mapped
        let container = try decodeMappedProperties(from: decoder)
        // you could decode any type as long is Codable and passing String as a Key
        let base64Image: String = try container.decode(forKey: "image")
        if let imageData: Data = Data(base64Encoded: base64Image) {
            self.image = UIImage(data: imageData)
        }
    }
    
    func encode(to encoder: Encoder) throws {
        // this will automatically encode all Mapped properties and return container which you could use to encode property that not mapped
        var container = try encodeMappedProperties(to: encoder)
        if let base64Image = self.image.pngData()?.base64EncodedString() {
            // you could encode any type as long is Codable and passing String as a Key
            container.encode(value: base64Image, forKey: "address")
        }
    }
}

手动映射

如果您对属性使用非 Codable 类型,或者您想在 Swift 属性中表示与真实属性不同的数据,您可以使用 @AlterMapped 属性而不是 @Mapped 并传递 TypeAlterer 作为转换器。 使用此方法,您无需手动实现 init(from:) throwsfunc encode(to:) throws

struct User: AlterCodable {
    
    @Mapped(key: "full_name")
    var fullName: String = ""
    
    @Mapped(key: "user_name")
    var userName: String = ""
    
    @Mapped
    var age: Int = 0
    
    // manual mapping
    @AlterMapped(alterer: Base64ImageAlterer(format: .png))
    var image: UIImage = .init()
    
    // manual mapping with key
    @AlterMapped(key: "birth_date", alterer: StringDateAlterer(pattern: "dd-MM-yyyy"))
    var birthDate: Date = .distantPast
}

如果您的数据类型是可选的、数组或两者,您可以使用 optionally 计算属性或 forArray 计算属性,甚至可以使用两者的组合,因为该属性是 TypeAlterer 协议的扩展。 属性调用的顺序会影响 TypeAlterer 类型的结果。

struct User: AlterCodable {
    
    @Mapped(key: "full_name")
    var fullName: String = ""
    
    @Mapped(key: "user_name")
    var userName: String = ""
    
    @Mapped
    var age: Int = 0
    
    @AlterMapped(key: "birth_date", alterer: StringDateAlterer(pattern: "dd-MM-yyyy"))
    var birthDate: Date = .distantPast
    
    // optional
    @AlterMapped(alterer: Base64ImageAlterer(format: .png).optionally)
    var image: UIImage? = nil
    
    // array
    @AlterMapped(key: "login_times", alterer: UnixLongDateAlterer().forArray)
    var loginTimes: [Date] = []
    
    // array optional
    @AlterMapped(key: "crashes_times", alterer: UnixLongDateAlterer().forArray.forOptional)
    var crashesTimes: [Date]? = nil
    
    // array of optional
    @AlterMapped(key: "some_times", alterer: UnixLongDateAlterer().forOptional.forArray)
    var someTimes: [Date?] = []
}

Alter 提供了一些原生 TypeAlterer,您可以直接使用

如果您想实现自己的 TypeAlterer,只需创建实现 TypeAlterer 的类或结构体。 Value 是属性值类型,AlteredValue 是编码值,应该实现 Codable

public struct MyOwnDataAlterer: TypeAlterer {
    public typealias Value = Data
    public typealias AlteredValue = String
    
    public init() { }
    
    public func alter(value: Data) -> String {
        value.base64EncodedString()
    }
    
    public func alterBack(value: String) -> Data {
        Data(base64Encoded: value) ?? .init()
    }
}

嵌套属性

Alter 可以使用 . 映射嵌套属性,例如

而不是这样做

struct Event: AlterCodable {
    
    @Mapped
    var title: String = ""
    
    @Mapped
    var ticket: Ticket = .init(price: 0)
    
    struct Ticket: AlterCodable {
        @Mapped
        var price: Double = 0
    }
}

您可以使用 "ticket.price" 键跳过 Ticket 对象

struct Event: AlterCodable {
    
    @Mapped
    var title: String = ""
    
    @Mapped(key: "ticket.price")
    var price: Double = 0
}

可变性

在大多数情况下,我们不希望我们的模型是可变的,但是由于 Alter 需要属性是可变的,以便可以在对象创建时进行分配,您可以只将 setter 设置为私有。

struct User: AlterCodable {
    
    @Mapped(key: "full_name")
    private(set) var fullName: String = ""
    
    @Mapped(key: "user_name")
    private(set) var userName: String = ""
    
    @Mapped
    private(set) var age: Int = 0
}

如果您希望具有可变能力来将 Alterable 视为 Dictionary,则有一些额外的功能。 任何实现 MutableAlterable 协议的对象都可以像 Dictionary 一样处理。

struct MutableUser: MutableAlterable {
    @Mapped(key: "user_name")
    var userName: String? = nil
    ...
    ...
    ...
}

或者使用 MutableAlterCodable,它是 MutableAlterable & Codabletypealias

struct MutableUser: MutableAlterCodable {
    @Mapped(key: "user_name")
    var userName: String? = nil
    ...
    ...
    ...
}

然后您可以像处理字典一样处理它

let user = MutableUser()
user[mappedKey: "user_name"] = "this is username"

// will print "this is username"
print(user.userName)

let userName: String = user[mappedKey: "user_name"] ?? ""

// will print "this is username"
print(userName)

下标可以接受任何类型,只要该类型可以强制转换为属性的真实类型或更改后的类型即可。