Created
November 21, 2014 06:30
-
-
Save brow/95ee90fb179746b6e318 to your computer and use it in GitHub Desktop.
FromJSONObject
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# FromJSONObject | |
FromJSONObject is a protocol adopted by Swift types that can be instantiated | |
from a dictionary of the sort produced by `NSJSONSerialization` -- and that | |
produce an informative error when they can't. | |
```swift | |
struct Person { | |
let name: String | |
let age: Int | |
var description: String { | |
return "\(name), a \(age)-year-old" | |
} | |
} | |
extension Person: FromJSONObject { /* ... */ } | |
Person.fromJSONObject(["name": "Joe", "age": 12]) | |
// Joe, a 12-year-old | |
Person.fromJSONObject(["name": "Dave", "age": "just a number"]) | |
// error: expected number for key "age" (README.md, line 43); got NSString | |
``` | |
FromJSONObject is also a module that makes adopting the FromJSONObject protocol | |
easy, declarative, and fun. The module supports things like: | |
* Optional properties | |
* Properties which are themselves FromJSONObject | |
* Properties which are collections | |
* Conditional properties for enums or class clusters | |
* Keypaths | |
## Adopting FromJSONObject | |
A typical implementation of FromJSONObject for `Person` is: | |
```swift | |
extension Person: FromJSONObject { | |
static func fromJSONObject(o: NSDictionary) -> Result<Person> { | |
o.need("name") >>- { name in | |
o.need("age") >>- { age in | |
return success( | |
Person(name: name, age: age)) | |
}} | |
} | |
} | |
``` | |
Invoking `need("age")` declares that the key `"age"` is required -- i.e., that | |
`fromJSONObject` produces an error if `o["age"] == nil`. | |
It is inferred from the parameter types of `Person()` that `o["name"]` and | |
`o["age"]` should be a string and a number respectively. If they're not, | |
`fromJSONObject` produces an error. | |
Any error produced by `fromJSONObject` will point to a line number in your | |
implementation of the method: | |
```swift | |
Person.fromJSONObject(["name": "Keanu"] | |
// error: expected key "age" (README.md, line 43) | |
``` | |
Line 43 is where we declared `o.need("age")`. | |
## Optional properties | |
Optional properties are declared using the `want` method: | |
```swift | |
struct Issue: FromJSONObject { | |
let assigneeID: Int? | |
var description: String { return "Assigned to \(assigneeID)" } | |
static func fromJSONObject(o: NSDictionary) -> Result<Issue> { | |
o.want("assignee_id") >>- { assigneeID in | |
return success( | |
Issue(assigneeID: assigneeID)) | |
} | |
} | |
} | |
``` | |
If the key is absent from the dictionary, then `assigneeID` is `nil`: | |
```swift | |
Issue.fromJSONObject([]) | |
// Assigned to nil | |
``` | |
An error is still produced if the key is present with an unexpected value type: | |
```swift | |
Issue.fromJSONObject(["assignee_id": "123"]) | |
// error: expected number for key "assignee_id" (README.md, line 78); got NSString | |
``` | |
### allowNull | |
Beware that `NSJSONSerialization` translates JSON's `null` to `NSNull()`. A key | |
with value `NSNull()` is not equivalent to an absent key: | |
```swift | |
Issue.fromJSONObject(["assignee_id": NSNull()]) | |
// error: expected number for key "assignee_id" (README.md, line 78); got NSNull | |
``` | |
If we want to interpret nulls as missing values, we can use the `allowNull` | |
parameter: | |
```swift | |
//... | |
o.want("assignee_id", allowNull: true) >>- { assigneeID in | |
// ... | |
``` | |
Now the null doesn't trigger an error: | |
```swift | |
Issue.fromJSONObject(["assignee_id": NSNull()]) | |
// Assigned to nil | |
``` | |
### Either<NSNull, T> | |
You may wish not only to allow nulls, but to treat them as distinct from missing | |
values. You can accompish this with `wantEither`. | |
```swift | |
static func fromJSONObject(o: NSDictionary) -> Result<Task> { | |
o.wantEither("assignee_id")>- { newAssigneeID in | |
return success( | |
IssueUpdate(newAssigneeID: newAssigneeID) | |
} | |
} | |
``` | |
## Object properties | |
JSON objects often contain other JSON objects. FromJSONObject can be composed | |
from other FromJSONObject types. | |
```swift | |
struct User: FromJSONObject { | |
let id: Int | |
let name: String | |
static func fromJSONObject(o: NSDictionary) -> Result<User> { | |
o.need("id") >>- { id in | |
o.need("name") >>- { name in | |
return success( | |
User(id: id, name: name)) | |
}} | |
} | |
} | |
struct Task: FromJSONObject { | |
let creator: User | |
static func fromJSONObject(o: NSDictionary) -> Result<Task> { | |
o.needObject("creator") >>- { creator in | |
return success( | |
Task(creator: creator)) | |
} | |
} | |
} | |
``` | |
Errors originating from a key in a nested object reflect that context: | |
```swift | |
Task.fromJSONObject(["creator": ["id": 123]]) | |
// error: in object "creator" (README.md, line 163), expected key "name" (README.md, line 152) | |
``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment