Giter Site home page Giter Site logo

alembic's Introduction

Alembic

Build Status Swift2.2 Lincense Platform
CocoaPods Carthage Swift Package Manager

Functional JSON parsing, mapping to objects, and serialize to JSON


Contents


Features

  • JSON parsing with ease
  • Mapping JSON to objects
  • Serialize objects to JSON
  • class, struct, enum support with non-optional let properties
  • Powerful value transformation
  • Fail safety
  • Functional, Protocol-oriented concepts
  • Flexible syntaxes

A trait of Alembic is to enable easy JSON parsing and transform its value by data streams.
It's the same also in the value-parsing or object-mapping.
Followings is simple example of json parsing.

Overview

let j = JSON(obj)

Value parsing

let str1: String = try j.distil("str1")
let str2: String = try j <| "str2"
let str3: String = try j["str3"].distil()

// โ†“ Same as `j["str4"].success {}` or `(j <| "str4").success {}`
j.distil("str4")(String).success {
    let str4 = $0
}

Object mapping

struct User {
    let name: String
    let avatarUrl: NSURL
}

let user: User = try j.distil("user")(JSON).map { userJson in
    try User(
        name: userJson <| "name",
        avatarUrl: (userJson <| "avatar_url").flatMap(NSURL.init(string:))
    )
}
struct User: Distillable {
    let name: String
    let avatarUrl: NSURL

    static func distil(j: JSON) throws -> User {
        return try User(
            name: j <| "name",
            avatarUrl: (j <| "avatar_url").flatMap(NSURL.init(string:))
        )
    }
}

let user: User = try j <| "user"

Usage

Initialization

import Alembic

JSON from AnyObject

let j = JSON(jsonObject)

JSON from NSData

let j = try JSON(data: jsonData)
let j = try JSON(data: jsonData, options: .AllowFragments)

JSON from String

let j = try JSON(string: jsonString)
let j = try JSON(
    string: jsonString,
    encoding: NSUTF8StringEncoding,
    allowLossyConversion: false,
    options: .AllowFragments
)

JSON parsing

To enable parsing, a class, struct, or enum just needs to implement the Distillable protocol.

public protocol Distillable {
    static func distil(j: JSON) throws -> Self
}

Default supported types

  • JSON
  • String
  • Int
  • Double
  • Float
  • Bool
  • NSNumber
  • Int8
  • UInt8
  • Int16
  • UInt16
  • Int32
  • UInt32
  • Int64
  • UInt64
  • RawRepresentable
  • Array<T: Distillable>
  • Dictionary<String, T: Distillable>

Example

let jsonObject = ["key": "string"]
let j = JSON(jsonObject)

function

let string: String = try j.distil("key")  // "string"

custom operator

let string: String = try j <| "key"  // "string"

subscript

let string: String = try j["key"].distil()  // "string"

Tips
You can set the generic type as following:

let string = try j.distil("key").to(String)  // "string"

It's same if use operator or subscript

Nested objects parsing

Supports parsing nested objects with keys and indexes.
Keys and indexes can be summarized in the same array.

Example

let jsonObject = [
    "nested": ["array": [1, 2, 3, 4, 5]]
]
let j = JSON(jsonObject)

function

let int: Int = try j.distil(["nested", "array", 2])  // 3        

custom operator

let int: Int = try j <| ["nested", "array", 2]  // 3  

subscript

let int: Int = try j["nested", "array", 2].distil()  // 3  
let int: Int = try j["nested"]["array"][2].distil()  // 3  

Tips
Syntax like SwiftyJSON is here:

let json = try JSON(data: jsonData)
let userName = try json[0]["user"]["name"].to(String)

Optional objects parsing

Has functions to parsing optional objects.
If the key is missing, returns nil.

Example

let jsonObject = [
    "nested": [:] // Nested key is nothing...
]
let j = JSON(jsonObject)

function

let int: Int? = try j.option(["nested", "key"])  // nil

custom operator

let int: Int? = try j <|? ["nested", "key"]  // nil

subscript

let int: Int? = try j["nested", "key"].option()  // nil
let int: Int? = try j["nested"]["key"].option()  // nil

Custom objects parsing

If implement Distillable protocol to existing classes like NSURL, it be able to parse from JSON.

Example

let jsonObject = ["key": "http://example.com"]
let j = JSON(jsonObject)
extension NSURL: Distillable {
    public static func distil(j: JSON) throws -> Self {
        return try j.distil().flatMap(self.init(string:))
    }
}

let url: NSURL = try j <| "key"  // http://example.com

Object mapping

To mapping your models, need confirm to the Distillable protocol.
Then, parse the objects from JSON to all your model properties.

Example

let jsonObject = [
    "key": [
        "string_key": "string",
        "option_int_key": NSNull()
    ]
]
let j = JSON(jsonObject)
struct Sample: Distillable {
    let string: String
    let int: Int?

    static func distil(j: JSON) throws -> Sample {
        return try Sample(
            string: j <| "string_key",
            int: j <|? "option_int_key"
        )
    }
}

let sample: Sample = try j <| "key"  // Sample

Value transformation

Alembic supports functional value transformation during the parsing process like String -> NSDate.
Functions that extract value from JSON are possible to return Distillate object.
So, you can use 'map' 'flatMap' and other following useful functions.

func description returns throws
map(Value throws -> U) Transform the current value. U throw
flatMap(Value throws -> (U: DistillateType)) Returns the value containing in U. U.Value throw
flatMap(Value throws -> U? Returns the non-nil value.
If the transformed value is nil,
throw DistillError.FilteredValue
U.Wrapped throw
mapError(ErrorType throws -> ErrorType If the error thrown, replace its error. Value throw
flatMapError(ErrorType throws -> (U: DistillateType) If the error thrown, flatMap its error. U.Value throw
filter(Value throws -> Bool) If the value is filtered by predicates,
throw DistillError.FilteredValue.
Value throw
recover(Value) If the error was thrown, replace it.
Error handling is not required.
Value (might replace)
recover(ErrorType -> Value) If the error was thrown, replace it.
Error handling is not required.
Value (might replace)
replaceNil(Value.Wrapped) If the value is nil, replace it. Value.Wrapped (might replace) throw
replaceNil(() throws -> Value.Wrapped) If the value is nil, replace it. Value.Wrapped (might replace) throw
filterNil() If the value is nil,
throw DistillError.FilteredValue.
Value.Wrapped throw
replaceEmpty(Value) If the value is empty of CollectionType, replace it. Value (might replace) throw
replaceEmpty(() throws -> Value) If the value is empty of CollectionType, replace it. Value (might replace) throw
filterEmpty() If the value is empty of CollectionType,
throw DistillError.FilteredValue.
Value throw

Example

let jsonObject = ["time_string": "2016-04-01 00:00:00"]
let j = JSON(jsonObject)

function

let date: NSDate = j.distil("time_string")(String)  // "Apr 1, 2016, 12:00 AM"
    .filter { !$0.isEmpty }
    .flatMap { dateString in
        let fmt = NSDateFormatter()
        fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
        return fmt.dateFromString(dateString)
    }
    .recover(NSDate())

Tips
When the transforming streams is complicated, often generic type is missing.
At that time, set the type explicitly as following:

let value: String = try j.distil("number")(Int).map { "Number \($0)" }
let value: String = try (j <| "number")(Int).map { "Number \($0)" }
let value: String = try j["number"].distil(Int).map { "Number \($0)" }

You can create Distillate by Distillate.just(value), Distillate.filter() and Distillate.error(error).
It's provide more convenience to value-transformation.
Example:

struct FindAppleError: ErrorType {}

let message: String = try j.distil("number_of_apples")(Int)
    .flatMap { count -> Distillate<String> in
        count > 0 ? .just("\(count) apples found!!") : .filter()
    }
    .flatMapError { _ in Distillate.error(FindAppleError()) }
    .recover { error in "Anything not found... | Error: \(error)" }

Error handling

Alembic has simple error handling designs as following.

DistillError

  • case MissingPath(JSONPath)
  • case TypeMismatch(expected: Any.Type, actual: AnyObject)
  • case FilteredValue(type: Any.Type, value: Any)
func null missing key type mismatch error in sub-objects
try j.distil(path)
try j <| path
try j[path].distil()
throw throw throw throw
try j.option(path)
try j <|? path
try j[path].option()
nil nil throw throw
try? j.distil(path)
try? j <| path
try? j[path].distil()
nil nil nil nil
try? j.option(path)
try? j <|? path
try? j[path].option()
nil nil nil nil

Don't wanna handling the error?
If you don't care about error handling, use try? or j.distil("key").recover(value).

let value: String? = try? j.distil("key")
let value: String = j.distil("key").recover("sub-value")

Receive a value by the data streams

Alembic allows you to receive a value parsed from JSON by the data streams.

let jsonObject = ["user": ["name": "john doe"]]
let j = JSON(jsonObject)
j.distil(["user", "name"])(String)
    .map { name in "User name is \(name)" }
    .success { message in
        print(message)
    }
    .failure { error in
        // Do error handlling
    }

Serialize objects to JSON

To Serialize objects to NSData or String of JSON, your models should implements the Serializable protocol.

public protocol Serializable {
    func serialize() -> JSONObject
}

serialize() function returns the JSONObject.

  • JSONObject
    init with Array<T: JSONValueConvertible> or Dictionary<String, T: JSONValueConvertible> only.
    Implemented the ArrayLiteralConvertible and DictionaryLiteralConvertible.
  • JSONValueConvertible
    The protocol that to be convert to JSONValue with ease.
  • JSONValue
    For constraint to the types that allowed as value of JSON.

Defaults JSONValueConvertible implemented types

  • String
  • Int
  • Double
  • Float
  • Bool
  • NSNumber
  • Int8
  • UInt8
  • Int16
  • UInt16
  • Int32
  • UInt32
  • Int64
  • UInt64
  • RawRepresentable
  • JSONValue

Example

let user: User = ...
let data = JSON.serializeToData(user)
let string = JSON.serializeToString(user)

enum Gender: String, JSONValueConvertible {
    case Male = "male"
    case Female = "female"

    private var jsonValue: JSONValue {
        return JSONValue(rawValue)
    }
}

struct User: Serializable {
    let id: Int
    let name: String    
    let gender: Gender
    let friendIds: [Int]

    func serialize() -> JSONObject {
        return [
            "id": id,
            "name": name,            
            "gender": gender,
            "friend_ids": JSONValue(friendIds)
        ]
    }
}

More Example

See the Alembic Tests for more examples.
If you want to try Alembic, use Alembic Playground :)


Requirements

  • Swift 2.2 / Xcode 7.3
  • OS X 10.9 or later
  • iOS 8.0 or later
  • watchOS 2.0 or later
  • tvOS 9.0 or later

Installation

Add the following to your Podfile:

use_frameworks!
pod 'Alembic'

Add the following to your Cartfile:

github "ra1028/Alembic"

Add the following to your Seedfile:

github "ra1028/Alembic", :files => "Sources/**/*.swift"

Add the following to your Package.swift:

let package = Package(
    name: "ProjectName",
    dependencies: [
        .Package(url: "https://github.com/ra1028/Alembic.git", majorVersion: 1)
    ]
)

Playground

  1. Open Alembic.xcworkspace.
  2. Build the Alembic-iOS.
  3. Open Alembic playground in project navigator.
  4. Enjoy the Alembic!

Contribution

Welcome to fork and submit pull requests!!

Before submitting pull request, please ensure you have passed the included tests.
If your pull request including new function, please write test cases for it.

(Also, welcome the offer of Alembic logo image ๐Ÿ™)


About

Alembic is inspired by great libs Argo, Himotoki, RxSwift.
Greatly thanks for authors!! ๐Ÿป.


License

Alembic is released under the MIT License.


Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.