FunctionalJSON is a fast and functional JSON library for Swift.
Inspired by the play/scala JSON lib.
- Simple reads composition to build complex structures
- Full JSON validation & easy debugging
- Easy navigation into the JSON tree
- Simple syntax
- Fast !
json :
{
"customers" : [
{
"name" : "alice",
"age" : 20,
"transactions" : [{"id" : 21312},{"id" : 32414},{"id" : 23443}]
},
{
"name" : "bob",
"transactions" : []
},
{
"name" : "chris",
"age" : 34,
"transactions" : [{"id" : 23455},{"id" : 23452}]
}
]
}swift :
import FunctionalJSON
import FunctionalBuilder
struct Person : JSONReadable {
let name : String
let age : Int?
let transactions : [Transaction]
static let jsonRead = JSONRead(
JSONPath("name").read(String) <&>
JSONPath("age").read(Int?) <&>
JSONPath("transactions").read([Transaction])
).map(Person.init)
}
struct Transaction : JSONReadable {
let identifier : Int64
static let jsonRead = JSONPath("id").read(Int64).map(Transaction.init)
}
let jsonData : NSData = ...
let json = try JSONValue(data : jsonData)
let persons : [Person] = try json["customers"].validate([Person]) CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:
$ gem install cocoapodsTo integrate FunctionalJSON into your Xcode project using CocoaPods, specify it in your Podfile:
platform :ios, '8.0'
use_frameworks!
pod 'FunctionalJSON', '~> 0.1.0'Then, run the following command:
$ pod installCarthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
You can install Carthage with Homebrew using the following command:
$ brew update
$ brew install carthageTo integrate FunctionalJSON into your Xcode project using Carthage, specify it in your Cartfile:
github "kreactive/FunctionalJSON" ~> 0.1.0
Run carthage to build the framework and drag the built FunctionalJSON.framework and FunctionalBuilder.framework into your Xcode project.
JSONValue struct contains parsed json data.
let jsonData : NSData = ...
let json = try JSONValue(data : jsonData)The input data is parsed using Foundation NSJSONSerialization.
Parsing option can be passed as an initializer parameter :
let json = try JSONValue(data: jsonData, options : [.AllowFragments])Navigate using subscript and a JSONPath :
let jsonElement : JSONValue = json[JSONPath("customers",0)]or
let jsonElement : JSONValue = json["customers"][0] <br />or :
let jsonElement : JSONValue = json["customers",0]JSONPath is wrapper around a array of JSONPathComponent
public enum JSONPathComponent {
case Key(String)
case Index(Int)
}A Key value represents the key of json object and an Index represents the index in a json array.
This method will always return a JSONValue, even if there's no corresponding value in the json tree.
The isNull property will return true if there is no value.
let isNull : Bool = json["customers",1992002].isNullThe `isEmpty` property will return `true` if there is no underlying value or is an empty object or array.
```swift let isEmpty : Bool = json["customers"].isEmpty ```
JSONRead<T> struct defines how a value is read from a json. It contains the path to the element and the function that will validate and transform that element to the target value of type T.
All basic json types are implemented and mapped to the swift types. (Int..,Double,Float,String,Array)
JSONRead can be transformed using
map<U>(t : T throws -> U) -> JSONRead<U>- Read an
Intvalue at path "customers"/0/"age" :
let read : JSONRead<Int> = JSONPath(["customers",0,"age"]).read(Int)- Transform to an NSDate read
let readDate : JSONRead<NSDate> = read.map {
guard let date = NSCalendar.currentCalendar().dateByAddingUnit(.Year,
value: -$0,
toDate: NSDate(),
options: []) else {
throw Error.DateError
}
return date
}- Get an optional read if it fails:
let optionalRead : JSONRead<NSDate?> = readDate.optional- Or a default value if the read fails :
let defaultDateRead : JSONRead<NSDate> = readDate.withDefault(NSDate())let jsonValue : JSONValue = ...
do {
let date : NSDate = try jsonValue.validate(defaultDateRead)
} catch {
...
}public protocol JSONReadable {
static var jsonRead : JSONRead<Self> {get}
}The JSONReadable protocol is used to get the default read of a type. It can't be implemented on a non-final class because subclasses can't redeclare jsonRead static var for its type.
This protocol enables the "type" syntax in JSONValue validation and JSONPath read methods :
JSONPath("name").read(String)instead of
JSONPath("name").read(String.jsonRead)Composition and the <&> operator come from the FunctionalBuilder module. This module is used to compose generic throwing functions and accumulate errors. You can composite up to 10 reads.
let read : JSONRead<(String,Int?,[Transaction])> = JSONRead(
JSONPath("name").read(String) <&>
JSONPath("age").read(Int?) <&>
JSONPath("transactions").read([Transaction])
)Unlike most json libs, the validation does not stop at the first error. Instead, all error are accumulated and reported as a flat array of errors at the end.
Validating this JSON with the previous reads :
{
"name" : 31232,
"age" : 30,
"transactions" : [{"identifier" : 23455},{"id" : 23455}]
}```swift let json = try JSONValue(data : jsonData) do { try json.validate(Person) } catch { print(error) } ```
This will throw a JSONValidationError that contains 2 errors :
JSON Errors :
JSON Bad value type -> "name"
JSON Value not found -> "transactions/0/id"