一个灵活的 Swift 框架,用于类和结构体与 JSON 之间的转换,并支持诸如 Realm 等存储解决方案。
iOS 10.0+ Swift 5.0+
对于 iOS 8 使用 v 0.12.0,请参阅 swift-3
标签。对于 Swift 3 使用 (v 0.6.0..<0.7.0),请参阅 swift-4.0
标签。对于 Swift 4.0,请参阅 swift-4.2
标签。对于 Swift 4.2
platform :ios, '10.0'
use_frameworks!
pod 'Crust'
dependencies: [
.package(url: "https://github.com/rexmas/Crust.git", .upToNextMinor(from: "0.13.0"))
]
可以映射到/从类或结构体
class Company {
var employees = Array<Employee>()
var uuid: String = ""
var name: String = ""
var foundingDate: NSDate = NSDate()
var founder: Employee?
var pendingLawsuits: Int = 0
}
如果您不需要存储(通常结构体就是这种情况),请使用 AnyMappable
。
struct Person: AnyMappable {
var bankAccounts: Array<Int> = [ 1234, 5678 ]
var attitude: String = "awesome"
var hairColor: HairColor = .Unknown
var ownsCat: Bool? = nil
}
Crust 的设计理念是 关注点分离。 它不对用户希望从 JSON 映射和映射到 JSON 的方式数量以及用户希望存储其模型的各种方式做出任何假设。
Crust 有 2 个基本协议
Mapping
- 如何将 JSON 映射到特定模型以及从特定模型映射到 JSON - (模型由 associatedtype MappedObject
设置,如果要映射到对象序列,则设置 associatedtype SequenceKind
)。 - 可能包括主键和嵌套映射。PersistanceAdapter
- 如何从后备存储(例如 Core Data、Realm 等)存储和检索用于映射的模型对象。当不需要存储 PersistanceAdapter
时,还有 2 个额外的协议
AnyMappable
- 由要映射到 JSON 和从 JSON 映射的模型(类或结构体)继承。AnyMapping
- 不需要 PersistanceAdapter
的 Mapping
。对于不同的用例,每个模型可以创建的各种 Mapping
和 PersistanceAdapter
的数量没有限制。
Crust 依赖于 JSONValue 作为其 JSON 编码和解码机制。 它提供了许多优点,包括类型安全、下标和通过协议的可扩展性。
创建一组 MappingKey
,用于定义从 JSON payload 到模型的键路径。
enum EmployeeKey: MappingKey {
case uuid
case name
case employer(Set<CompanyKey>)
var keyPath: String {
switch self {
case .employer(_): return "company"
case .uuid: return "data.uuid" // This means our JSON has a 'data' payload we're elevating.
case .name: return "data.name"
}
}
// You can specifically specify what keys you'd like to map from in the `keyedBy` argument of the mapper. This function retrieves the nested keys.
func nestedMappingKeys<Key: MappingKey>() -> AnyKeyCollection<Key>? {
switch self {
case .employer(let companyKeys):
return companyKeys.anyKeyCollection()
default:
return nil
}
}
}
enum CompanyKey: MappingKey {
case uuid
case name
case employees(Set<EmployeeKey>)
case founder(Set<EmployeeKey>)
case foundingDate
case pendingLawsuits
var keyPath: String {
switch self {
case .uuid: "uuid"
case .name: "name"
case .employees(_): "employees"
case .founder(_): "founder"
case .foundingDate: "data.founding_date"
case .pendingLawsuits: "data.lawsuits_pending"
}
}
func nestedMappingKeys<Key: MappingKey>() -> AnyKeyCollection<Key>? {
switch self {
case .employees(let employeeKeys):
return employeeKeys.anyKeyCollection()
case .founder(let employeeKeys):
return employeeKeys.anyKeyCollection()
default:
return nil
}
}
}
如果使用存储,请使用 Mapping
为您的模型创建映射;如果未使用存储,请使用 AnyMapping
。
使用存储(假设 CoreDataAdapter
符合 PersistanceAdapter
)
class EmployeeMapping: Mapping {
var adapter: CoreDataAdapter
var primaryKeys: [Mapping.PrimaryKeyDescriptor]? {
// property == attribute on the model, keyPath == keypath in the JSON blob, transform == tranform to apply to data from JSON blob.
return [ (property: "uuid", keyPath: EmployeeKey.uuid.keyPath, transform: nil) ]
}
required init(adapter: CoreDataAdapter) {
self.adapter = adapter
}
func mapping(inout toMap: inout Employee, payload: MappingPayload<EmployeeKey>) throws {
// Company must be transformed into something Core Data can use in this case.
let companyMapping = CompanyTransformableMapping()
// No need to map the primary key here.
toMap.employer <- (.mapping(.employer([]), companyMapping), payload)
toMap.name <- (.name, payload)
}
}
不使用存储
class CompanyMapping: AnyMapping {
// associatedtype MappedObject = Company is inferred by `toMap`
func mapping(inout toMap: inout Company, payload: MappingPayload<CompanyKey>) throws {
let employeeMapping = EmployeeMapping(adapter: CoreDataAdapter())
toMap.employees <- (.mapping(.employees([]), employeeMapping), payload)
toMap.founder <- (.mapping(.founder([]), employeeMapping), payload)
toMap.uuid <- (.uuid, payload)
toMap.name <- (.name, payload)
toMap.foundingDate <- (.foundingDate, payload)
toMap.pendingLawsuits <- (.pendingLawsuits, payload)
}
}
创建您的 Crust Mapper。
let mapper = Mapper()
使用 mapper 转换到 JSONValue
对象和从 JSONValue
对象转换
let json = try! JSONValue(object: [
"uuid" : "uuid123",
"name" : "name",
"employees" : [
[ "data" : [ "name" : "Fred", "uuid" : "ABC123" ] ],
[ "data" : [ "name" : "Wilma", "uuid" : "XYZ098" ] ]
]
"founder" : NSNull(),
"data" : [
"lawsuits_pending" : 5
],
// Works with '.' keypaths too.
"data.founding_date" : NSDate().toISOString(),
]
)
// Just map 'uuid', 'name', 'employees.name', 'employees.uuid'
let company: Company = try! mapper.map(from: json, using: CompanyMapping(), keyedBy: [.uuid, .name, .employees([.name, .uuid])])
// Or if json is an array and you'd like to map everything.
let company: [Company] = try! mapper.map(from: json, using: CompanyMapping(), keyedBy: AllKeys())
注意: 可以通过 json.values()
将 JSONValue
转换回 AnyObject
类型的 json 变体,并通过 try! json.encode()
转换回 NSData
。
Crust 支持嵌套模型的嵌套映射。例如,从上面
func mapping(inout toMap: Company, payload: MappingPayload<CompanyKey>) throws {
let employeeMapping = EmployeeMapping(adapter: CoreDataAdapter())
toMap.employees <- (Binding.mapping(.employees([]), employeeMapping), payload)
}
Binding
在映射集合时提供专门的指令。 使用 .collectionMapping
case 通知 mapper 这些指令。 它们包括
Element
遵循 Equatable
,则去重会自动进行。Element
不遵循 Equatable
,则除非显式提供 UniquingFunctions
并且使用映射函数 map(toCollection field:, using binding:, uniquing:)
,否则将忽略去重。下表提供了一些示例,说明如何根据被映射到的 Collection 类型以及 nullable
的值以及 JSON payload 中是否存在值或 "null" 值来映射 "null" json 值。
append / replace | nullable | vals / null | Array | Array? | RLMArray |
---|---|---|---|---|---|
append | yes or no | vals | append | append | append |
append | yes | null | no-op | no-op | no-op |
replace | yes or no | vals | replace | replace | replace |
replace | yes | null | removeAll | assign null | removeAll |
append or replace | no | null | error | error | error |
默认情况下,使用 .mapping
将 (insert: .replace(delete: nil), unique: true, nullable: true)
。
public enum CollectionInsertionMethod<Container: Sequence> {
case append
case replace(delete: ((_ orphansToDelete: Container) -> Container)?)
}
public typealias CollectionUpdatePolicy<Container: Sequence> =
(insert: CollectionInsertionMethod<Container>, unique: Bool, nullable: Bool)
public enum Binding<M: Mapping>: Keypath {
case mapping(Keypath, M)
case collectionMapping(Keypath, M, CollectionUpdatePolicy<M.SequenceKind>)
}
用法
let employeeMapping = EmployeeMapping(adapter: CoreDataAdapter())
let binding = Binding.collectionMapping("", employeeMapping, (.replace(delete: nil), true, true))
toMap.employees <- (binding, payload)
有关更多信息,请参阅 ./Mapper/MappingProtocols.swift。
每个 mapping
都传递一个 Payload: MappingPayload<T>
,该 Payload
必须在映射期间包含。 payload
包括从映射传播回调用方的错误信息以及有关被映射到/从 JSON 映射到对象的上下文信息。
要在映射期间包含 payload,请将其作为元组包含。
func mapping(inout toMap: Company, payload: MappingPayload<CompanyKey>) throws {
toMap.uuid <- (.uuid, payload)
toMap.name <- (.name, payload)
}
要创建简单的自定义转换(例如基本值类型),请实现 Transform
协议
public protocol Transform: AnyMapping {
func fromJSON(_ json: JSONValue) throws -> MappedObject
func toJSON(_ obj: MappedObject) -> JSONValue
}
并像任何其他 Mapping
一样使用它。
允许为同一模型提供多个 Mapping
。
class CompanyMapping: AnyMapping {
func mapping(inout toMap: Company, payload: MappingPayload<CompanyKey>) throws {
toMap.uuid <- (.uuid, payload)
toMap.name <- (.name, payload)
}
}
class CompanyMappingWithNameUUIDReversed: AnyMapping {
func mapping(inout toMap: Company, payload: MappingPayload<CompanyKey>) throws {
toMap.uuid <- (.name, payload)
toMap.name <- (.uuid, payload)
}
}
只需使用两个不同的映射。
let mapper = Mapper()
let company1 = try! mapper.map(from: json, using: CompanyMapping(), keyedBy: AllKeys())
let company2 = try! mapper.map(from: json, using: CompanyMappingWithNameUUIDReversed(), keyedBy: AllKeys())
遵循 PersistanceAdapter
协议将数据存储到 Core Data、Realm 等中。
符合 PersistanceAdapter
的对象必须包含两个 associatedtype
BaseType
- 此存储系统的模型对象的顶级类。NSManagedObject
。RLMObject
。Object
。ResultsType: Collection
- 用于对象查找。 应设置为 BaseType
的集合。然后 Mapping
必须设置其 associatedtype AdapterKind = <Your Adapter>
以在映射期间使用它。
./RealmCrustTests
中包含一些测试,其中包含如何将 Crust 与 realm-cocoa (Obj-C) 一起使用的示例。
如果您希望将 Crust 与 RealmSwift 一起使用,请查看此(稍微过时的)存储库以获取示例。 https://github.com/rexmas/RealmCrust
欢迎 pull requests!
swift test --generate-linuxmain
MIT 许可证 (MIT)
版权所有 (c) 2015-2018 Rex
特此授予任何人免费获得本软件及其相关文档文件(“软件”)的副本的权利,以不受限制地处理本软件,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售本软件的副本的权利,并允许向其提供本软件的人员这样做,但须符合以下条件
上述版权声明和本许可声明应包含在本软件的所有副本或重要部分中。
本软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于适销性、特定用途适用性和非侵权性的保证。 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是在合同、侵权行为或其他方面,由本软件或本软件的使用或其他处理引起的或与之相关的。