Unbox 4.0.0

Unbox 4.0.0

测试已测试
Lang语言 SwiftSwift
许可 MIT
发布最新版本2019年4月
SPM支持SPM

John Sundell 维护。



Unbox 4.0.0

Unbox

Unbox | Wrap

Travis status CocoaPods Carthage Twitter: @johnsundell

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类型,如

  • 布尔值
  • IntDoubleFloat
  • 字符串
  • 数组
  • 字典

它还支持嵌套数组和字典的所有可能组合。正如您在上面的中看到的那样(其中正在使用unboxable的Astronaut结构体的数组进行拆包),我们甚至可以仅通过一次简单的unbox()调用就拆包复杂的数据结构。

最后,它还支持通过使用转换器来支持的URL,以及其他任何DateFormatter使用的Date

转换

Unbox还支持转换,可以帮助您将任何值或对象当作原始JSON类型来处理。

它自带一个默认的StringURL的转换,允许您从描述URL的字符串中提取任何URL属性,而无需编写任何转换代码。

对于StringInt, 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
})

希望您喜欢打开您的JSON!

有关Unbox的最新消息和我的其他开源项目,请关注我的Twitter:@johnsundell

请确保查看Wrap,它可以让您轻松编码 JSON。