Skip to content

Instantly share code, notes, and snippets.

@brow
Created November 21, 2014 06:30
Show Gist options
  • Save brow/95ee90fb179746b6e318 to your computer and use it in GitHub Desktop.
Save brow/95ee90fb179746b6e318 to your computer and use it in GitHub Desktop.
FromJSONObject
# 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&lt;NSNull, T&gt;
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