|
| 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 | + |
0 commit comments