- 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.
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"
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
)
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
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)
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
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
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
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)" }
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")
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
}
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
withArray<T: JSONValueConvertible>
orDictionary<String, T: JSONValueConvertible>
only.
Implemented theArrayLiteralConvertible
andDictionaryLiteralConvertible
. - JSONValueConvertible
The protocol that to be convert toJSONValue
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)
]
}
}
See the Alembic Tests
for more examples.
If you want to try Alembic, use Alembic Playground :)
- 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
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)
]
)
- Open Alembic.xcworkspace.
- Build the Alembic-iOS.
- Open Alembic playground in project navigator.
- Enjoy the Alembic!
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 ๐)
Alembic is inspired by great libs
Argo,
Himotoki,
RxSwift.
Greatly thanks for authors!! ๐ป.
Alembic is released under the MIT License.