Skip to content
This repository has been archived by the owner on Oct 14, 2020. It is now read-only.

Commit

Permalink
Updates to the README
Browse files Browse the repository at this point in the history
Adds test cases to verify example code is valid
Returns `json.serialized()` convenience
Added section on JSONDecodable
Added Badges
  • Loading branch information
vdka committed Apr 26, 2016
1 parent 43b7ad8 commit 5ad3ab2
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 6 deletions.
4 changes: 4 additions & 0 deletions JSON.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
AAB69AA21CC8D9C300940C5D /* JSONSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB69A9E1CC8D9C300940C5D /* JSONSerializer.swift */; };
AAB69AA31CC8D9C300940C5D /* JSONSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB69A9E1CC8D9C300940C5D /* JSONSerializer.swift */; };
AAB69AA51CC8FB5300940C5D /* SerializerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB69AA41CC8FB5300940C5D /* SerializerUnitTests.swift */; };
AAC7F5911CCE5083001DF64A /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC7F5901CCE5083001DF64A /* ReadmeTests.swift */; };
AAFB64541CC66F3900C269E3 /* ParserUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFB64531CC66F3900C269E3 /* ParserUnitTests.swift */; };
AAFB645C1CC6F09500C269E3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFB645B1CC6F09500C269E3 /* main.swift */; };
AAFB64601CC6F0DB00C269E3 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA213B251CBC800800910D50 /* JSON.swift */; };
Expand Down Expand Up @@ -111,6 +112,7 @@
AA853B0F1CBE9E4700FD3970 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AAB69A9E1CC8D9C300940C5D /* JSONSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONSerializer.swift; sourceTree = "<group>"; };
AAB69AA41CC8FB5300940C5D /* SerializerUnitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SerializerUnitTests.swift; path = JSON/SerializerUnitTests.swift; sourceTree = "<group>"; };
AAC7F5901CCE5083001DF64A /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = "<group>"; };
AAFB64531CC66F3900C269E3 /* ParserUnitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ParserUnitTests.swift; path = JSON/ParserUnitTests.swift; sourceTree = "<group>"; };
AAFB64591CC6F09500C269E3 /* Profiling */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Profiling; sourceTree = BUILT_PRODUCTS_DIR; };
AAFB645B1CC6F09500C269E3 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -221,6 +223,7 @@
AA213B3A1CBC8A7B00910D50 /* JSONTests.swift */,
AAFB64531CC66F3900C269E3 /* ParserUnitTests.swift */,
AAB69AA41CC8FB5300940C5D /* SerializerUnitTests.swift */,
AAC7F5901CCE5083001DF64A /* ReadmeTests.swift */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -560,6 +563,7 @@
buildActionMask = 2147483647;
files = (
AAFB64541CC66F3900C269E3 /* ParserUnitTests.swift in Sources */,
AAC7F5911CCE5083001DF64A /* ReadmeTests.swift in Sources */,
AAB69AA51CC8FB5300940C5D /* SerializerUnitTests.swift in Sources */,
AA213B3B1CBC8A7B00910D50 /* JSONTests.swift in Sources */,
);
Expand Down
2 changes: 1 addition & 1 deletion JSONPerformanceTests/JSONPerformanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class JSONPerformanceTests: XCTestCase {
}
}

func testVDKASerializerSpeed() {
func testSerializerSpeed() {
measureBlock {
do {
try JSON.Serializer.serialize(json)
Expand Down
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# JSON

[![Language](https://img.shields.io/badge/Swift-2.2-brightgreen.svg)](http://swift.org)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)

This library makes dealing with JSON feel more _native_ to Swift.

Internally JSON is represented as follows in a simple `enum`. This means there is no
Expand Down Expand Up @@ -59,7 +62,7 @@ extension Person: JSONEncodable {
return [
"name": name,
"age": age,
"accountBalances": accountsBalances.encoded(),
"accountBalances": accountsBalances.encoded(), // encoded is called for _container_ objects like `Array<T>`, `Optional<T>`
"totalBalance": totalBalance
]
}
Expand Down Expand Up @@ -104,16 +107,16 @@ print(try! JSONSerializer.serialize(person, options: [.prettyPrint]))
extension Currency: JSONDecodable {} // RawRepresentable types have a default implementation. You must still conform though.
extension Money: JSONDecodable {
static func decode(json: JSON) throws -> Money {
let minorUnits = try json["minorUnits"].int ?? raise(JSON.Error.BadField("minorUnits"))
let minorUnits = try json["minorUnits"].int ?? JSON.Error.BadField("minorUnits")
let currency: Currency = try json.get("currency")
return Money(minorUnits: minorUnits, currency: currency)
}
}

extension Person: JSONDecodable {
static func decode(json: JSON) throws -> Person {
let name = try json["name"].string ?? raise(JSON.Error.BadField("name"))
let age = try json["age"].int ?? raise(JSON.Error.BadField("age"))
let name = try json["name"].string ?? JSON.Error.BadField("name")
let age = try json["age"].int ?? JSON.Error.BadField("age")
let accountBalances: [Money] = try json["accountBalances"].array?.flatMap(Money.init) ?? []
return Person(name: name, age: age, accountBalances: accountBalances)
}
Expand Down Expand Up @@ -166,6 +169,22 @@ extension JSON: DictionaryLiteralConvertible {
}
```

## JSONDecodable
Conforming types can be decoded and initialized directly from JSON
```swift
public protocol JSONDecodable {
init(json: JSON) throws
static func decode(json: JSON) throws -> Self
}

// Default implementation
extension JSONDecodable {
public init(json: JSON) throws {
self = try Self.decode(json)
}
}
```

## Parser
```swift
JSONParser.parse(string: String, options: [JSONParser.Option] = []) throws -> JSON
Expand All @@ -178,6 +197,19 @@ JSONSerializer.serialize(json: JSON, options: [JSONSerializer.Option] = []) thro
```
Throws `iff` Double values are non finite.

## Operators

The `??` operator has been overloaded for convencience allowing throw on nil behaviour.
```swift
infix operator ?? { associativity right precedence 131 } // matches stdlib ??

/// Throws the error on the right side. Use to throw on nil.
public func ??<T>(lhs: T?, @autoclosure error: () -> ErrorType) throws -> T {
guard case .Some(let value) = lhs else { throw error() }
return value
}
```

# Language Limitations
- Protocol extensions cannot have add conformance to other protocols [radar](http://www.openradar.me/23433955)
- Concrete type extensions cannot add conformance and have a where clause
Expand Down
10 changes: 9 additions & 1 deletion Sources/JSONEncodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public protocol JSONEncodable {

// MARK: - JSON Conformance to JSONEncodable

//// NOTE: Is this necessary?
extension JSON: JSONEncodable {
public init(_ value: JSONEncodable) {
self = value.encoded()
Expand All @@ -30,6 +29,15 @@ extension JSON: JSONEncodable {
}


// MARK: - Add `serialized` to `JSONEncodable`

extension JSONEncodable {
public func serialized(options options: [JSON.Serializer.Option] = []) throws -> String {
return try JSON.Serializer.serialize(self.encoded(), options: options)
}
}


// NOTE: track rdar://23433955


Expand Down
110 changes: 110 additions & 0 deletions Tests/ReadmeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//
// ReadmeTests.swift
// JSON
//
// Created by Ethan Jackwitz on 4/25/16.
// Copyright © 2016 Ethan Jackwitz. All rights reserved.
//

import XCTest
import JSON

class ReadmeTests: XCTestCase {

func testReadmeExample() { // Would be awkward if the readme examples didn't work.

// assert possible
_ = try! Money.decode(["minorUnits": 1000, "currency": "AUD"])
_ = try! Money(json: ["minorUnits": 1000, "currency": "AUD"])

[
"name": "Harry",
"age": 20,
"accountBalances": [
["minorUnits": 10000, "currency": "AUD"] as JSON,
["minorUnits": 1000, "currency": "AUD"] as JSON,
["minorUnits": -20000, "currency": "AUD"] as JSON
] as JSON
] as JSON

let savings = Money(minorUnits: 10000, currency: .AUD)
let spending = Money(minorUnits: 1000, currency: .AUD)
let studentLoans = Money(minorUnits: -20000, currency: .AUD)
let person = Person(name: "Harry", age: 20, accountBalances: [spending, savings, studentLoans])

try! print(person.serialized(options: [.prettyPrint]))

// gross.
let expectedOutput = "{\n \"totalBalance\": {\n \"currency\": \"AUD\",\n \"minorUnits\": -9000\n },\n \"age\": 20,\n \"accountBalances\": [\n {\n \"currency\": \"AUD\",\n \"minorUnits\": 1000\n },\n {\n \"currency\": \"AUD\",\n \"minorUnits\": 10000\n },\n {\n \"currency\": \"AUD\",\n \"minorUnits\": -20000\n }\n ],\n \"name\": \"Harry\"\n}"

try! XCTAssertEqual(person.serialized(options: [.prettyPrint]), expectedOutput)

}

}

enum Currency: String { case AUD }

struct Money {
var minorUnits: Int
var currency: Currency
}

func + (lhs: Money, rhs: Money) -> Money {
guard lhs.currency == rhs.currency else { fatalError("Must be the same currency") }
return Money(minorUnits: lhs.minorUnits + rhs.minorUnits, currency: rhs.currency)
}

struct Person {
var name: String
var age: Int
var jobTitle: String?
var accountBalances: [Money]
var totalBalance: Money {
return accountBalances.reduce(Money(minorUnits: 0, currency: .AUD), combine: +)
}
init(name: String, age: Int, jobTitle: String? = nil, accountBalances: [Money]) {
self.name = name
self.age = age
self.jobTitle = jobTitle
self.accountBalances = accountBalances
}
}

extension Currency: JSONEncodable {}

extension Money: JSONEncodable {
func encoded() -> JSON {
return ["minorUnits": minorUnits, "currency": currency]
}
}

extension Person: JSONEncodable {
func encoded() -> JSON {
return [
"name": name,
"age": age,
"jobTitle": jobTitle.encoded(),
"accountBalances": accountBalances.encoded(),
"totalBalance": totalBalance
]
}
}

extension Currency: JSONDecodable {}
extension Money: JSONDecodable {
static func decode(json: JSON) throws -> Money {
let minorUnits = try json["minorUnits"].int ?? JSON.Error.BadField("minorUnits")
let currency: Currency = try json.get("currency")
return Money(minorUnits: minorUnits, currency: currency)
}
}

extension Person: JSONDecodable {
static func decode(json: JSON) throws -> Person {
let name = try json["name"].string ?? JSON.Error.BadField("name")
let age = try json["age"].int ?? JSON.Error.BadField("age")
let accountBalances: [Money] = try json["accountBalances"].array?.flatMap(Money.init) ?? []
return Person(name: name, age: age, accountBalances: accountBalances)
}
}

0 comments on commit 5ad3ab2

Please sign in to comment.