Pistachio 0.1.2

Pistachio 0.1.2

TestsTested
LangLanguage SwiftSwift
License MIT
ReleasedLast Release Mar 2015
SPMSupports SPM

Maintained by Alexsander Akers.



Pistachio 0.1.2

Pistachio is a generic model framework for Swift. By leveraging lenses and value transformers, it allows you to create type safe adapters for any recursive data structure, be it JSON, YAML or XML.

If you are already familiar with Argo, take a look at Pistachiargo.

Installation

Usage

Let's start with defining a simple model:

struct Origin {
  var city: String

  init(city: String = "") {
    self.city = city
  }
}
struct Person {
  var name: String
  var origin: Origin

  init(name: String = "", origin: Origin = Origin()) {
    self.name = name
    self.origin = origin
  }
}

A lens is basically just a combination of a getter and a setter, providing a view on your model:

struct OriginLenses {
  static let city = Lens(get: { $0.city }, set: { (inout origin: Origin, city) in
    origin.city = city
  })
}
struct PersonLenses {
  static let name = Lens(get: { $0.name }, set: { (inout person: Person, name) in
    person.name = name
  })

  static let origin = Lens(get: { $0.origin }, set: { (inout person: Person, origin) in
    person.origin = origin
  })
}

They can be used to access and modify your model:

var person = Person(name: "Felix", origin: Origin(city: "Berlin"))
person = set(PersonLenses.name, person, "Robb")
get(PersonLenses.name, person) // == "Robb"

And you can compose, lift, transform, ... them:

let composed = PersonLenses.origin >>> OriginLenses.city
person = set(composed, person, "New York")
get(composed, person) // == "New York"
var persons = [ person ]

let arrayLifted: Lens<[Person], [String]> = lift(composed)
persons = set(arrayLifted, [ person ], [ "San Francisco" ])
get(arrayLifted, persons) // == [ "San Francisco" ]
var result: Result<[Person], NSError> = success(persons)

let resultLifted: Lens<Result<[Person], NSError>, Result<[String], NSError>> = lift(arrayLifted)
result = set(resultLifted, result, success([ "London" ]))
get(resultLifted, result) // == .Success(Box([ "London" ]))
let valueTransformer: ValueTransformer<String, Int> = SocialSecurityNumberValueTransformer

let transformed = transform(PersonLenses.name, valueTransformer)
person = set(transformed, person, 1234567890)
get(PersonLenses.name, person) // == "Felix"

Value transformers can be flipped, composed and lifted:

let flipped = flip(valueTransformer)
flipped.transformedValue(1234567890) // == "Felix"
let composed = flipped >>> UppercaseValueTransformer
flipped.transformedValue(1234567890) // == "FELIX"
let dictionaryLifted = lift([ "Felix": 1234567890 ], 0, "Unknown")
dictionaryLifted.transformedValue("Felix") // == 1234567890
dictionaryLifted.transformedValue("Hans") // == 0
dictionaryLifted.reverseTransformedValue(1234567890) // == "Felix"
dictionaryLifted.reverseTransformedValue(0) // == "Unknown"
let optionalLifted = lift(UppercaseValueTransformer, "")
optionalLifted.transformedValue("Felix") // == "FELIX"
optionalLifted.transformedValue(nil) // == ""
optionalLifted.reverseTransformedValue("FELIX") // == "felix"
optionalLifted.reverseTransformedValue("") // == nil
let arrayLifted = lift(UppercaseValueTransformer)
arrayLifted.transformedValue([ "Felix", "Robb" ]) // == [ "FELIX", "ROBB" ]

With lenses and value transformers, you can create adapters for your models:

struct Adapters {
  static let origin = DictionaryAdapter(specification: [
    "city_name": transform(OriginLenses.city, StringToAnyObjectValueTransformers)
  ], dictionaryTansformer: DictionaryToAnyObjectValueTransformers)

  static let person = DictionaryAdapter(specification: [
    "name": transform(PersonLenses.name, StringToAnyObjectValueTransformers),
    "origin": transform(PersonLenses.origin, lift(origin, Origin()))
  ], dictionaryTansformer: DictionaryToAnyObjectValueTransformers)
}

Uh, what was that? Right, the origin adapter was lifted into a value transformer.

Use fix to create adapters for recursive models:

let adapter: DictionaryAdapter<Model, Data, Error> = fix { adapter in
  // use `adapter` to reference the currently created adapter
}

Adapters handle encoding to and decoding from data:

let adapter = Adapters.person

var person = Person(name: "Seb", origin: Origin(city: "Berlin"))
var data = adapter.encode(person)
// == .Success(Box([ "name": "Seb", "origin": [ "city_name": "Berlin" ] ]))

adapter.decode(Person(), from: data.value!)
// == .Success(Box(person))

Both encode and decode return a LlamaKit.Result, which either holds the encoded/decoded value or an error. This enables you to gracefully handle coding errors.

Posts

About

Pistachio was built by Felix Jendrusch and Robert Böhnke. Greetings from Berlin :wave: