TestsTested | ✓ |
LangLanguage | SwiftSwift |
License | MIT |
ReleasedLast Release | Apr 2016 |
SPMSupports SPM | ✓ |
Maintained by Jonathan Landon.
An iOS framework for creating JSON-based models. Written in Swift (because it totally rules!)
Embedded frameworks require a minimum deployment target of iOS 8
The Swift Package Manager is a dependency management tool provided by Apple, still in early design and development. For more infomation check out its GitHub Page.
You can use the Swift Package Manager to install ModelRocket
by adding it as a dependency in your Package.swift
file:
import PackageDescription
let package = Package(
name: "PROJECT_NAME",
targets: [],
dependencies: [
.Package(url: "https://github.com/ovenbits/ModelRocket.git", versions: "1.2.3" ..< Version.max)
]
)
class Vehicle: Model {
let make = Property<String>(key: "make")
let model = Property<String>(key: "model", required: true)
let year = Property<Int>(key: "year") { year in
if year < 2015 {
// offer discount
}
}
let color = Property<UIColor>(key: "color", defaultValue: UIColor.blackColor())
}
NOTE: As with all Swift variables,
let
should always be used, unlessvar
is absolutely needed. In the case of Model objects,let
should be used for allProperty[Array|Dictionary]
properties, as it still allows the underlyingvalue
to be changed, unless you truly need to reassign the property
String
Bool
Int
UInt
Double
Float
In addition to the core types above, ModelRocket also supports serialization for several other classes out of the box:
NSDate
— ISO8601-formatted string (2015-05-31T19:00:17.000+0000
)UIColor
— hex-color string (#f6c500
)NSURL
— any url string (http://ovenbits.com
)NSNumber
— any number, can be used in place of Double
, Float
, Int
, and UInt
// `Model` subclasses get `fromJSON` and `toJSON` implementations on `JSONTransformable` for free,
// but explicit `JSONTransformable` conformance is still required
extension Vehicle: JSONTransformable {}
class Vehicles: Model {
let vehicles = PropertyArray<Vehicle>(key: "vehicles")
}
PropertyArray
conforms to CollectionType
, therefore, the .values
syntax is not necessary when iterating through the values. For example:
let allVehicles = Vehicles(json: <json>)
// using `.values` syntax
for vehicle in allVehicles.vehicles.values {
}
// using `CollectionType` conformance
for vehicle in allVehicles.vehicles {
}
class Car: Vehicle {
let purchasedTrims = PropertyDictionary<Int>(key: "purchased_trims")
}
PropertyDictionary
conforms to CollectionType
, therefore, the .values
syntax is not necessary when iterating through the keys and values. For example:
let vehicle = Vehicle(json: <json>)
// using `.values` syntax
for (key, trim) in vehicle.purchasedTrims.values {
}
// using `CollectionType` conformance
for (key, trim) in vehicle.purchasedTrims {
}
NOTE: All object in the dictionary must be of the same type. If they’re not, the app won’t crash, but values of different types will be discarded
// instantiate object
let vehicle = Vehicle(json: json)
// get property type
println("Vehicle make property has type: \(vehicle.make.type)")
// get property value
if let make = vehicle.make.value {
println("Vehicle make: \(make)")
}
Model objects also contain a failable initializer, which will only return an initialized object if all properties marked as required = true
are non-nil.
// instantiate object, only if `json` contains a value for the `make` property
if let vehicle = Vehicle(strictJSON: json) {
// it's best to avoid implicitly unwrapped optionals, however, since `vehicle` is initialized iff `make` is non-nil, if can be force-unwrapped safely here
println("Vehicle make: \(vehicle.make.value!)")
}
else {
pintln("Invalid JSON")
}
class Car: Vehicle {
let numberOfDoors = Property<Int>(key: "number_of_doors")
}
The custom object must conform to the JSONTransformable protocol by defining the following variables/functions
class Vehicle: Model {
let manufacturer = Property<Manufacturer>(key: "manufacturer")
}
class Manufacturer: Model {
let companyName = Property<String>(key: "company_name")
let headquarters = Property<String>(key: "headquarters")
let founded = Property<NSDate>(key: "founded")
}
extension Manufacturer: JSONTransformable {
class func fromJSON(json: JSON) -> Manufacturer? {
return Manufacturer(json: json)
}
func toJSON() -> AnyObject {
return self.json().dictionary
}
}
ModelRocket supports enum types for Property[Array|Dictionary]
properties, as long as the enum conforms to the JSONTransformable
protocol.
As a simple example, the material type of a vehicle’s interior could use an enum like this:
enum VehicleInterior: String {
case Fabric = "fabric"
case Leather = "leather"
}
extension VehicleInterior: JSONTransformable {
static func fromJSON(json: JSON) -> VehicleInterior? {
return VehicleInterior(rawValue: json.stringValue)
}
func toJSON() -> AnyObject {
return rawValue
}
}
class Vehicle: ModelRocket {
let interior = Property<VehicleInterior>(key: "interior")
}
postProcess
hookThe Property
postProcess
closure (also available on PropertyArray
and PropertyDictionary
) provides a mechanism for work to be done after all properties of a Model
object have been initialized from JSON but before the Model
object has finished initializing.
class Vehicles: Model {
let vehicles = PropertyArray<Vehicle>(key: "vehicles") { (values) -> Void in
for vehicle in values {
println("postHook vehicle: \(vehicle.make.value!)")
}
}
}
.value
accessor usage patternA ModelRocket property is of type Property<T>
. When accessing the property’s value, you go through Property.value
, e.g.:
let vehicleMake = make.value
It is perfectly acceptable to to utilize Property
s and access the property value
directly. However, you may want a different public API for your model objects.
private let _make = Property<String>(key: "make")
public var make: String {
get {
return make.value ?? "unknown make"
}
set {
make.value = newValue
}
}
This usage pattern enables:
Property
value
is optional because it must be for general applicability, but your API may be more correct to have non-optional. Of course, if your API wants an optional, that’s fine too.Property.value
could be set, you could omit the set accessor for a read-only property, again helping to expose exactly the API for your object that you desire.Override the modelForJSON(json: JSON) -> Model
function
class Vehicle: Model {
let make = Property<String>(key: "make")
let model = Property<String>(key: "model")
let year = Property<Int>(key: "year")
let color = Property<UIColor>(key: "color")
let manufacturer = Property<Manufacturer>(key: "manufacturer")
override class func modelForJSON(json: JSON) -> Vehicle {
switch json["type"].stringValue {
case "car":
return Car(json: json)
case "plane":
return Plane(json: json)
case "bike":
return Bike(json: json)
default:
return Vehicle(json: json)
}
}
}
Then to access subclass-specific properties, use a switch-case
let vehicle = Vehicle.modelForJSON(vehicleJSON)
switch vehicle {
case let car as Car:
// drive the car
case let plane as Plane:
// fly the plane
case let bike as Bike:
// ride the bike
default:
// do nothing
}
Calling the json()
function on a Model subclass returns a tuple containing:
dictionary: [String : AnyObject]
json: JSON
data: NSData
Call copy()
on the object, and cast to the correct type. Example:
let vehicleCopy = vehicle.copy() as! Vehicle
ModelRocket is released under the MIT license. See LICENSE for details.