Skip to content

Commit 1474ba6

Browse files
authored
Merge pull request #122 from mattpolzin/ditch-google-colab-examples
switch from google colab examples to markdown
2 parents 8825783 + 8cea47b commit 1474ba6

File tree

10 files changed

+1293
-13
lines changed

10 files changed

+1293
-13
lines changed

README.md

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,20 @@ See the JSON API Spec here: https://jsonapi.org/format/
77

88
## Quick Start
99

10-
:warning: The following Google Colab examples have correct code, but from time to time the Google Colab Swift compiler may be buggy and claim it cannot build the JSONAPI library.
11-
1210
### Clientside
13-
- [Basic Example](https://colab.research.google.com/drive/1IS7lRSBGoiW02Vd1nN_rfdDbZvTDj6Te)
14-
- [Compound Example](https://colab.research.google.com/drive/1BdF0Kc7l2ixDfBZEL16FY6palweDszQU)
15-
- [Metadata Example](https://colab.research.google.com/drive/10dEESwiE9I3YoyfzVeOVwOKUTEgLT3qr)
16-
- [Custom Errors Example](https://colab.research.google.com/drive/1TIv6STzlHrkTf_-9Eu8sv8NoaxhZcFZH)
17-
- [PATCH Example](https://colab.research.google.com/drive/16KY-0BoLQKiSUh9G7nYmHzB8b2vhXA2U)
18-
- [Resource Storage Example](https://colab.research.google.com/drive/196eCnBlf2xz8pT4lW--ur6eWSVAjpF6b?usp=sharing) (using [JSONAPI-ResourceStorage](#jsonapi-resourcestorage))
11+
- [Basic Example](./documentation/examples/basic-example.md)
12+
- [Compound Example](./documentation/examples/compound-example.md)
13+
- [Metadata Example](./documentation/examples/metadata-example.md)
14+
- [Custom Errors Example](./documentation/examples/custom-errors-example.md)
15+
- [PATCH Example](./documentation/examples/patch-example.md)
16+
- [Resource Storage Example](./documentation/examples/resource-storage-example.md) (using [JSONAPI-ResourceStorage](#jsonapi-resourcestorage))
1917

2018
### Serverside
21-
- [GET Example](https://colab.research.google.com/drive/1krbhzSfz8mwkBTQQnKUZJLEtYsJKSfYX)
22-
- [POST Example](https://colab.research.google.com/drive/1z3n70LwRY7vLIgbsMghvnfHA67QiuqpQ)
19+
- [GET Example](./documentation/examples/serverside-get-example.md)
20+
- [POST Example](./documentation/examples/serverside-post-example.md)
2321

2422
### Client+Server
25-
This library works well when used by both the server responsible for serialization and the client responsible for deserialization. Check out the [example](./documentation/client-server-example.md).
23+
This library works well when used by both the server responsible for serialization and the client responsible for deserialization. Check out the [example](./documentation/examples/client-server-example.md).
2624

2725
## Table of Contents
2826
- JSONAPI
@@ -34,7 +32,7 @@ This library works well when used by both the server responsible for serializati
3432
- [CocoaPods](#cocoapods)
3533
- [Running the Playground](#running-the-playground)
3634
- [Project Status](./documentation/project-status.md)
37-
- [Server & Client Example](./documentation/client-server-example.md)
35+
- [Server & Client Example](./documentation/examples/client-server-example.md)
3836
- [Usage](./documentation/usage.md)
3937
- [JSONAPI+Testing](#jsonapitesting)
4038
- [Literal Expressibility](#literal-expressibility)
@@ -91,7 +89,7 @@ Note that Playground support for importing non-system Frameworks is still a bit
9189

9290
## Deeper Dive
9391
- [Project Status](./documentation/project-status.md)
94-
- [Server & Client Example](./documentation/client-server-example.md)
92+
- [Server & Client Example](./documentation/examples/client-server-example.md)
9593
- [Usage Documentation](./documentation/usage.md)
9694

9795
# JSONAPI+Testing
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
2+
# JSONAPI Basic Example
3+
4+
We are about to walk through a basic example to show how easy it is to set up a
5+
simple model. Information on creating models that take advantage of more of the
6+
features from the JSON:API Specification can be found in the [README](https://github.com/mattpolzin/JSONAPI/blob/main/README.md).
7+
8+
The `JSONAPI` framework relies heavily on generic types so the first step will
9+
be to alias away some of the JSON:API features we do not need for our simple
10+
example.
11+
12+
```swift
13+
/// Our Resource objects will not have any metadata or links and they will be identified by Strings.
14+
typealias Resource<Description: JSONAPI.ResourceObjectDescription> = JSONAPI.ResourceObject<Description, NoMetadata, NoLinks, String>
15+
16+
/// Our JSON:API Documents will similarly have no metadata or links associated with them. Additionally, there will be no included resources.
17+
typealias SingleDocument<Resource: ResourceObjectType> = JSONAPI.Document<SingleResourceBody<Resource>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
18+
19+
typealias BatchDocument<Resource: ResourceObjectType> = JSONAPI.Document<ManyResourceBody<Resource>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
20+
```
21+
22+
The next step is to create `ResourceObjectDescriptions` and `ResourceObjects`.
23+
For our simple example, let's create a `Person` and a `Dog`.
24+
25+
```swift
26+
struct PersonDescription: ResourceObjectDescription {
27+
// by common convention, we will use the plural form
28+
// of the noun as the JSON:API "type"
29+
static let jsonType: String = "people"
30+
31+
struct Attributes: JSONAPI.Attributes {
32+
let firstName: Attribute<String>
33+
let lastName: Attribute<String>
34+
35+
// we mark this attribute as "nullable" because the user can choose
36+
// not to specify an age if they would like to.
37+
let age: Attribute<Int?>
38+
}
39+
40+
struct Relationships: JSONAPI.Relationships {
41+
// we will define "Dog" next
42+
let pets: ToManyRelationship<Dog, NoIdMetadata, NoMetadata, NoLinks>
43+
}
44+
}
45+
46+
// this typealias is optional, but it makes working with resource objects much
47+
// more user friendly.
48+
typealias Person = Resource<PersonDescription>
49+
50+
struct DogDescription: ResourceObjectDescription {
51+
static let jsonType: String = "dogs"
52+
53+
struct Attributes: JSONAPI.Attributes {
54+
let name: Attribute<String>
55+
}
56+
57+
// we could relate dogs back to their owners, but for the sake of this example
58+
// we will instead show how you would create a resource with no relationships.
59+
typealias Relationships = NoRelationships
60+
}
61+
62+
typealias Dog = Resource<DogDescription>
63+
```
64+
65+
At this point we have two objects that can decode JSON:API responses. To
66+
illustrate we can mock up a dog response and parse it.
67+
68+
```swift
69+
// snag Foundation for JSONDecoder
70+
import Foundation
71+
72+
let mockBatchDogResponse =
73+
"""
74+
{
75+
"data": [
76+
{
77+
"type": "dogs",
78+
"id": "123",
79+
"attributes": {
80+
"name": "Sparky"
81+
}
82+
},
83+
{
84+
"type": "dogs",
85+
"id": "456",
86+
"attributes": {
87+
"name": "Charlie Dog"
88+
}
89+
}
90+
]
91+
}
92+
""".data(using: .utf8)!
93+
94+
let decoder = JSONDecoder()
95+
96+
let dogsDocument = try! decoder.decode(BatchDocument<Dog>.self, from: mockBatchDogResponse)
97+
98+
let dogs = dogsDocument.body.primaryResource!.values
99+
100+
print("dogs parsed: \(dogs.count ?? 0)")
101+
```
102+
103+
To illustrate `ResourceObject` property access, we can loop over the dogs and
104+
print their names.
105+
106+
```swift
107+
for dog in dogs {
108+
print(dog.name)
109+
}
110+
```
111+
112+
Now let's parse a mocked `Person` response.
113+
114+
```swift
115+
let mockSinglePersonResponse =
116+
"""
117+
{
118+
"data": {
119+
"type": "people",
120+
"id": "88223",
121+
"attributes": {
122+
"first_name": "Lisa",
123+
"last_name": "Offenbrook",
124+
"age": null
125+
},
126+
"relationships": {
127+
"pets": {
128+
"data": [
129+
{
130+
"type": "dogs",
131+
"id": "123"
132+
},
133+
{
134+
"type": "dogs",
135+
"id": "456"
136+
}
137+
]
138+
}
139+
}
140+
}
141+
}
142+
""".data(using: .utf8)!
143+
144+
decoder.keyDecodingStrategy = .convertFromSnakeCase
145+
146+
let personDocument = try! decoder.decode(SingleDocument<Person>.self, from: mockSinglePersonResponse)
147+
```
148+
149+
Our `Person` object has both attributes and relationships. Generally what we care
150+
about when accessing relationships is actually the Id(s) of the resource(s); the
151+
loop below shows off how to access those Ids.
152+
153+
```swift
154+
let person = personDocument.body.primaryResource!.value
155+
156+
let relatedDogIds = person ~> \.pets
157+
158+
print("related dog Ids: \(relatedDogIds)")
159+
```
160+
161+
To wrap things up, let's throw our dog resources into a local cache and tie
162+
things together a bit. There are many ways to go about storing or caching
163+
resources clientside. For additional examples of more robust solutions, take a
164+
look at [JSONAPI-ResourceStorage](https://github.com/mattpolzin/JSONAPI-ResourceStorage).
165+
166+
```swift
167+
let dogCache = Dictionary(uniqueKeysWithValues: zip(dogs.map { $0.id }, dogs))
168+
169+
func cachedDog(_ id: Dog.Id) -> Dog? { return dogCache[id] }
170+
171+
print("Our person's name is \(person.firstName) \(person.lastName).")
172+
print("They have \((person ~> \.pets).count) pets named \((person ~> \.pets).compactMap(cachedDog).map { $0.name }.joined(separator: " and ")).")
173+
```
174+
File renamed without changes.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# JSONAPI Compound Example
2+
3+
We are about to walk through an example to show how easy it is to parse JSON:API
4+
includes. Information on creating models that take advantage of more of the
5+
features from the JSON:API Specification can be found in the [README](https://github.com/mattpolzin/JSONAPI/blob/main/README.md).
6+
7+
We will begin by quickly redefining the same types of `ResourceObjects` from the
8+
[Basic Example](https://github.com/mattpolzin/JSONAPI/blob/main/documentation/basic-example.md).
9+
10+
```swift
11+
typealias Resource<Description: JSONAPI.ResourceObjectDescription> = JSONAPI.ResourceObject<Description, NoMetadata, NoLinks, String>
12+
13+
struct PersonDescription: ResourceObjectDescription {
14+
15+
static let jsonType: String = "people"
16+
17+
struct Attributes: JSONAPI.Attributes {
18+
let firstName: Attribute<String>
19+
let lastName: Attribute<String>
20+
21+
let age: Attribute<Int?>
22+
}
23+
24+
struct Relationships: JSONAPI.Relationships {
25+
let pets: ToManyRelationship<Dog, NoIdMetadata, NoMetadata, NoLinks>
26+
}
27+
}
28+
29+
typealias Person = Resource<PersonDescription>
30+
31+
struct DogDescription: ResourceObjectDescription {
32+
static let jsonType: String = "dogs"
33+
34+
struct Attributes: JSONAPI.Attributes {
35+
let name: Attribute<String>
36+
}
37+
38+
typealias Relationships = NoRelationships
39+
}
40+
41+
typealias Dog = Resource<DogDescription>
42+
```
43+
44+
Next we will create similar `typealiases` for single and batch documents as we did
45+
in the **Basic Example**, but we will allow for an include type to be specified.
46+
47+
```swift
48+
/// Our JSON:API Documents will still have no metadata or links associated with them but they will allow us to specify an include type later.
49+
typealias SingleDocument<Resource: ResourceObjectType, Include: JSONAPI.Include> = JSONAPI.Document<SingleResourceBody<Resource>, NoMetadata, NoLinks, Include, NoAPIDescription, UnknownJSONAPIError>
50+
51+
typealias BatchDocument<Resource: ResourceObjectType, Include: JSONAPI.Include> = JSONAPI.Document<ManyResourceBody<Resource>, NoMetadata, NoLinks, Include, NoAPIDescription, UnknownJSONAPIError>
52+
```
53+
54+
Now let's define a mock response containing a single person and including any
55+
dogs that are related to that person.
56+
57+
```swift
58+
// snag Foundation for Data and JSONDecoder
59+
import Foundation
60+
61+
let mockSinglePersonResponse =
62+
"""
63+
{
64+
"data": {
65+
"type": "people",
66+
"id": "88223",
67+
"attributes": {
68+
"first_name": "Lisa",
69+
"last_name": "Offenbrook",
70+
"age": null
71+
},
72+
"relationships": {
73+
"pets": {
74+
"data": [
75+
{
76+
"type": "dogs",
77+
"id": "123"
78+
},
79+
{
80+
"type": "dogs",
81+
"id": "456"
82+
}
83+
]
84+
}
85+
}
86+
},
87+
"included": [
88+
{
89+
"type": "dogs",
90+
"id": "123",
91+
"attributes": {
92+
"name": "Sparky"
93+
}
94+
},
95+
{
96+
"type": "dogs",
97+
"id": "456",
98+
"attributes": {
99+
"name": "Charlie Dog"
100+
}
101+
}
102+
]
103+
}
104+
""".data(using: .utf8)!
105+
```
106+
107+
Parsing the above response looks almost identical to in the **Basic Example**. The
108+
key thing to note is that instead of specifying `NoIncludes` we specify
109+
`Include1<Dog>` below. This does not mean "include one dog," it means "include one
110+
type of thing, with that type being `Dog`." The `JSONAPI` framework comes with
111+
built-in support for `Include2<...>`, `Include3<...>` and many more. If you wanted to include
112+
both `Person` and `Dog` resources (perhaps because your primary `Person` resource had
113+
a "friends" relationship), you would use `Include2<Person, Dog>`.
114+
115+
```swift
116+
let decoder = JSONDecoder()
117+
decoder.keyDecodingStrategy = .convertFromSnakeCase
118+
119+
let includeDocument = try! decoder.decode(SingleDocument<Person, Include1<Dog>>.self, from: mockSinglePersonResponse)
120+
```
121+
122+
The `Person` is pulled out as before with `Document.body.primaryResource`. The dogs
123+
can be accessed from `Document.body.includes`; note that because multiple types of
124+
things can be included, we must specify that we want things of type `Dog` by using
125+
the `JSONAPI.Includes` subscript operator.
126+
127+
```swift
128+
let person = includeDocument.body.primaryResource!.value
129+
let dogs = includeDocument.body.includes![Dog.self]
130+
131+
print("Parsed person named \(person.firstName) \(person.lastName)")
132+
print("Parsed dogs named \(dogs.map { $0.name }.joined(separator: " and "))")
133+
```
134+

0 commit comments

Comments
 (0)