Unbox | Wrap
Unbox是一个易于使用的Swift JSON解码器。不要花费几个小时编写JSON解码代码 - 只需解包即可!
Unbox轻量级,非魔法,无需你进行子类化,让你的JSON符合特定的模式或完全改变您编写模型代码的方式。它可以在任何模式下轻松使用。
基本示例
假设你有你常用的 User
模型
struct User {
let name: String
let age: Int
}
可以使用以下JSON初始化
{
"name": "John",
"age": 27
}
要将此JSON解码到 User
实例,您只需让 User
遵守 Unboxable
并解包其属性即可
struct User {
let name: String
let age: Int
}
extension User: Unboxable {
init(unboxer: Unboxer) throws {
self.name = try unboxer.unbox(key: "name")
self.age = try unboxer.unbox(key: "age")
}
}
Unbox自动(或者,实际上是Swift)确定你的属性类型,并相应地解码它们。现在,我们可以这样解码一个 User
let user: User = try unbox(dictionary: dictionary)
或者甚至
let user: User = try unbox(data: data)
高级示例
第一个例子非常简单,但是Unbox可以为您解码最复杂的JSON结构,包括必需和可选值,而无需您编写任何额外的代码
struct SpaceShip {
let type: SpaceShipType
let weight: Double
let engine: Engine
let passengers: [Astronaut]
let launchLiveStreamURL: URL?
let lastPilot: Astronaut?
let lastLaunchDate: Date?
}
extension SpaceShip: Unboxable {
init(unboxer: Unboxer) throws {
self.type = try unboxer.unbox(key: "type")
self.weight = try unboxer.unbox(key: "weight")
self.engine = try unboxer.unbox(key: "engine")
self.passengers = try unboxer.unbox(key: "passengers")
self.launchLiveStreamURL = try? unboxer.unbox(key: "liveStreamURL")
self.lastPilot = try? unboxer.unbox(key: "lastPilot")
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd"
self.lastLaunchDate = try? unboxer.unbox(key: "lastLaunchDate", formatter: dateFormatter)
}
}
enum SpaceShipType: Int, UnboxableEnum {
case apollo
case sputnik
}
struct Engine {
let manufacturer: String
let fuelConsumption: Float
}
extension Engine: Unboxable {
init(unboxer: Unboxer) throws {
self.manufacturer = try unboxer.unbox(key: "manufacturer")
self.fuelConsumption = try unboxer.unbox(key: "fuelConsumption")
}
}
struct Astronaut {
let name: String
}
extension Astronaut: Unboxable {
init(unboxer: Unboxer) throws {
self.name = try unboxer.unbox(key: "name")
}
}
错误处理
解析JSON本质上是可能失败的操作。JSON可能存在意外的格式,或者可能缺少必需的值。幸运的是,Unbox能够优雅地处理丢失和不匹配的值,并使用Swift的do, try, catch
模式将错误返回给您。
您无需处理多种错误类型并自行进行验证,并且您始终可以通过抛出
的方式手动退出拆包过程。Unbox返回的所有错误类型都是UnboxError
。
支持的类型
Unbox支持解析所有标准JSON类型,如
布尔值
Int
,Double
,Float
字符串
数组
字典
它还支持嵌套数组和字典的所有可能组合。正如您在上面的Astronaut
结构体的数组进行拆包),我们甚至可以仅通过一次简单的unbox()
调用就拆包复杂的数据结构。
最后,它还支持通过使用转换器来支持的URL
,以及其他任何DateFormatter
使用的Date
。
转换
Unbox还支持转换,可以帮助您将任何值或对象当作原始JSON类型来处理。
它自带一个默认的String
到URL
的转换,允许您从描述URL的字符串中提取任何URL
属性,而无需编写任何转换代码。
对于String
到Int, Double, Float, CGFloat
的转换也同样如此。如果您正在解析一个数字类型,并且字符串被找到,那么该字符串会自动转换为该数字类型(如果可能的话)。
要使您的类型能够使用转换进行unbox操作,您只需使其符合UnboxableByTransform
并实现其协议方法即可。
以下是一个示例,它通过转换使原生的Swift UniqueIdentifier
类型可被unbox
struct UniqueIdentifier: UnboxableByTransform {
typealias UnboxRawValueType = String
let identifierString: String
init?(identifierString: String) {
if let UUID = NSUUID(uuidString: identifierString) {
self.identifierString = UUID.uuidString
} else {
return nil
}
}
static func transform(unboxedValue: String) -> UniqueIdentifier? {
return UniqueIdentifier(identifierString: unboxedValue)
}
}
格式化
如果您有需要在使用前进行格式化的值,Unbox支持使用格式化器自动格式化拆包的值。任何DateFormatter
都可以直接用来格式化日期,但您也可以添加自定义类型的格式化器,如下所示
enum Currency {
case usd(Int)
case sek(Int)
case pln(Int)
}
struct CurrencyFormatter: UnboxFormatter {
func format(unboxedValue: String) -> Currency? {
let components = unboxedValue.components(separatedBy: ":")
guard components.count == 2 else {
return nil
}
let identifier = components[0]
guard let value = Int(components[1]) else {
return nil
}
switch identifier {
case "usd":
return .usd(value)
case "sek":
return .sek(value)
case "pln":
return .pln(value)
default:
return nil
}
}
}
您现在可以使用给定的CurrencyFormatter
轻松解析任何Currency
。
struct Product: Unboxable {
let name: String
let price: Currency
init(unboxer: Unboxer) throws {
name = try unboxer.unbox(key: "name")
price = try unboxer.unbox(key: "price", formatter: CurrencyFormatter())
}
}
支持 JSON,根对象为 Array 或 Dictionary
无论是您要解包的 JSON 的根对象是 Array
还是 Dictionary
,您都可以使用相同的 Unbox()
函数,Unbox 将根据类型推断返回单个模型或模型数组。
内建枚举支持
您还可以直接解包 枚举
,无需处理它们未能初始化的情况。您需要做的只是让任何您希望解包的 枚举
类型遵循 UnboxableEnum
,如下所示
enum Profession: Int, UnboxableEnum {
case developer
case astronaut
}
现在 Profession
可以在任何模型中直接解包
struct Passenger: Unboxable {
let profession: Profession
init(unboxer: Unboxer) throws {
self.profession = try unboxer.unbox(key: "profession")
}
}
上下文对象
有时候在解码过程中,您需要使用额外的数据而不是字典中包含的数据。为此,Unbox 支持强类型上下文对象,这些对象可以在解包初始化器中提供。
要使用上下文对象,让您的类型遵循 UnboxableWithContext
,然后可以使用 unbox(dictionary:context)
进行解包,其中 context
是您选择的类型。
键路径支持
您也可以使用键路径(用于字典键和数组索引)从嵌套 JSON 结构中解包值。让我们扩展我们的 User 模型
{
"name": "John",
"age": 27,
"activities": {
"running": {
"distance": 300
}
},
"devices": [
"Macbook Pro",
"iPhone",
"iPad"
]
}
struct User {
let name: String
let age: Int
let runningDistance: Int
let primaryDeviceName: String
}
extension User: Unboxable {
init(unboxer: Unboxer) throws {
self.name = try unboxer.unbox(key: "name")
self.age = try unboxer.unbox(key: "age")
self.runningDistance = try unboxer.unbox(keyPath: "activities.running.distance")
self.primaryDeviceName = try unboxer.unbox(keyPath: "devices.0")
}
}
您还可以使用键路径直接解包嵌套 JSON 结构。当您只需要从 JSON 主体中提取特定对象(或多个对象)时,这非常有用。
{
"company": {
"name": "Spotify",
},
"jobOpenings": [
{
"title": "Swift Developer",
"salary": 120000
},
{
"title": "UI Designer",
"salary": 100000
},
]
}
struct JobOpening {
let title: String
let salary: Int
}
extension JobOpening: Unboxable {
init(unboxer: Unboxer) throws {
self.title = try unboxer.unbox(key: "title")
self.salary = try unboxer.unbox(key: "salary")
}
}
struct Company {
let name: String
}
extension Company: Unboxable {
init(unboxer: Unboxer) throws {
self.name = try unboxer.unbox(key: "name")
}
}
let company: Company = try unbox(dictionary: json, atKey: "company")
let jobOpenings: [JobOpening] = try unbox(dictionary: json, atKey: "jobOpenings")
let featuredOpening: JobOpening = try unbox(dictionary: json, atKeyPath: "jobOpenings.0")
自定义解包
有时您需要更细粒度的控制解码过程,尽管Unbox设计为简单,但它也提供了一个强大的自定义解箱API,允许您控制对象是如何解箱的。这在与Core Data使用时,使用依赖注入或从多个来源聚合数据时都非常有用。以下是一个示例
let dependency = DependencyManager.loadDependency()
let model: Model = try Unboxer.performCustomUnboxing(dictionary: dictionary, closure: { unboxer in
var model = Model(dependency: dependency)
model.name = try? unboxer.unbox(key: "name")
model.count = try? unboxer.unbox(key: "count")
return model
})
CocoaPods
将行 pod "Unbox"
添加到您的 Podfile
Carthage
在您的 Cartfile
中添加行 github "johnsundell/unbox"
手动
克隆仓库并将文件 Unbox.swift
拖入您的 Xcode 项目中。
Swift包管理器
将行 .Package(url: "https://github.com/johnsundell/unbox.git", from: "3.0.0")
添加到您的 Package.swift
Unbox支持以下最低版本的Apple平台
- iOS 8
- OS X 10.11
- watchOS 2
- tvOS 9
如果您发现解箱代码没有按预期工作,以下是一些调试技巧
编译时错误: 对成员 'unbox' 的不明确引用
Swift无法找到要调用的适当的unbox
方法的重载。请确保您已经符合了任何所需的协议(例如Unboxable
、UnboxableEnum
等)。请注意,对于每个类型,您只能符合一个Unbox协议(即,一个类型不能既是UnboxableEnum
又是UnboxableByTransform
)。还请注意,您只能引用具体类型(不是Protocol
类型),这样Swift才能选择要使用哪个重载。
unbox()
抛出异常
使用do, try, catch
模式来捕获和处理错误
do {
let model: Model = try unbox(data: data)
} catch {
print("An error occurred: \(error)")
}
如果您在使用Unbox时遇到任何问题,欢迎打开一个Issue。
- UnboxedAlamofire - 使用Alamofire配合Unbox的最简单方法
希望您喜欢打开您的JSON!
有关Unbox的最新消息和我的其他开源项目,请关注我的Twitter:@johnsundell
请确保查看Wrap,它可以让您轻松编码 JSON。