|
1 | 1 | # Jsona
|
2 |
| -Framework agnostic library that provide systemized way to work with JSON API [v1.0 spetification](http://jsonapi.org/format/1.0/) in your JavaScript / TypeScript code. |
| 2 | +Framework agnostic library that provide data formatters to simplify work with JSON API [v1.0 specification](http://jsonapi.org/format/1.0/). |
3 | 3 |
|
4 | 4 | [](https://www.npmjs.com/package/jsona/)
|
5 | 5 |
|
6 |
| -### Why you may need it? |
7 |
| - |
8 |
| -If you use or build API, that specified by *json:api* and: |
9 |
| -- want to use more comfortable interface, than plain json:api object gives (such as array in `included`) to work with data in code; |
10 |
| -- dont't want to think how to build correct json in accordance with standard for POST/PUT/PATH requests; |
11 |
| -- like to have deal with typed data and you want to map API's entities to objects, that instantiated with unique constructors (classes); |
| 6 | +*NOTE:* This README describes 1.x.x version. You can read [README for old versions 0.2.x here](https://github.com/olosegres/jsona/README_0_2.md) |
12 | 7 |
|
13 | 8 | ### What it gives?
|
| 9 | +- converter from json to simplified objects (some denormalized structure) |
| 10 | +- converter from "reduxObject" to simplified objects (`reduxObject` is a result object of [json-api-normalizer](https://github.com/yury-dymov/json-api-normalizer) |
| 11 | +- converter from simplified objects to json (json in according with JSON API specification) |
14 | 12 |
|
15 |
| -Ability to automatically convert request body (json, formatted in accordance with specification *json:api*) to instances of predefined by you classes and back to correct json, easy. |
| 13 | +### How to use |
| 14 | +You need to instantiate Jsona ones, then use it's public methods to convert data. |
| 15 | +``` |
| 16 | +const dataFormatter = new Jsona(); |
| 17 | +``` |
16 | 18 |
|
17 |
| -**Simple example** |
| 19 | +##### deserialize - creates simplified object(s) from json |
18 | 20 | ```javascript
|
19 |
| -import {Jsona} from 'jsona'; |
20 |
| - |
21 |
| -// it suppose that we already defined special classes, such as data models |
22 |
| -// and EntitiesFactory, that will help Jsona to map json:api entities to our data models and back |
23 |
| -import MyEntitiesFactory from './MyEntitiesFactory'; |
24 |
| -import TestEntity1 from './entities/TestEntity1'; |
25 |
| -import TestEntity2 from './entities/TestEntity2'; |
26 |
| - |
27 |
| -const testJson = { |
28 |
| - "data": { |
29 |
| - "id": 123, |
30 |
| - "type": "testentity1", |
31 |
| - "attributes": { |
32 |
| - "foo1": "bar1" |
| 21 | +const json = { |
| 22 | + data: { |
| 23 | + type: 'town', |
| 24 | + id: '123', |
| 25 | + attributes: { |
| 26 | + name: 'Barcelona', |
| 27 | + }, |
| 28 | + relationships: { |
| 29 | + country: { |
| 30 | + data: { |
| 31 | + type: 'country', |
| 32 | + id: '32', |
| 33 | + } |
| 34 | + } |
| 35 | + } |
33 | 36 | },
|
34 |
| - "relationships": { |
35 |
| - "testrelation": { |
36 |
| - "data": { |
37 |
| - "id": 321, |
38 |
| - "type": "testentity2" |
| 37 | + included: [{ |
| 38 | + type: 'country', |
| 39 | + id: '32', |
| 40 | + attributes: { |
| 41 | + name: 'Spain', |
39 | 42 | }
|
40 |
| - } |
41 |
| - } |
42 |
| - }, |
43 |
| - "included": [{ |
44 |
| - "id": 321, |
45 |
| - "type": "testentity2", |
46 |
| - "attributes": { |
47 |
| - "foo2": "bar2" |
48 |
| - } |
49 |
| - }] |
| 43 | + }] |
50 | 44 | };
|
51 | 45 |
|
52 |
| -const dataFormatter = new Jsona(new MyEntitiesFactory()); |
53 |
| - |
54 |
| -// testJson may be stringified json or plain object |
55 |
| -const deserialized = dataFormatter.deserialize(testJson); |
56 |
| - |
57 |
| -console.log(deserialized); // will output something similar to: |
58 |
| -// { |
59 |
| -// hasCollection: false |
60 |
| -// hasItem: true |
61 |
| -// item: TestEntity1 { |
62 |
| -// foo1: "bar1" |
63 |
| -// id: 123 |
64 |
| -// type: "testentity1" |
65 |
| -// testrelation: TestEntity2 { |
66 |
| -// foo2: "bar2" |
67 |
| -// id: 321 |
68 |
| -// type: "testentity2" |
69 |
| -// } |
70 |
| -// } |
71 |
| -// collection: null |
72 |
| -// } |
73 |
| - |
| 46 | +const model = dataFormatter.deserialize(json); |
| 47 | +console.log(model); // will output: |
| 48 | +/* { |
| 49 | + type: 'town', |
| 50 | + id: '21', |
| 51 | + name: 'Shanghai', |
| 52 | + country: { |
| 53 | + type: 'country', |
| 54 | + id: '34', |
| 55 | + name: 'Spain' |
| 56 | + } |
| 57 | +} */ |
74 | 58 | ```
|
75 | 59 |
|
76 |
| -### Examples of helpers and models that you may change and use in your project |
77 |
| -Library written in TypeScript, but examples below uses ES6-7 and partly Flow. |
78 |
| - |
79 |
| -**MyEntitiesFactory.js** |
| 60 | +##### serialize - creates json from simplified object(s) |
80 | 61 | ```javascript
|
81 |
| -import Town from './entities/Town'; |
82 |
| -import Country from './entities/Country'; |
83 |
| -import Region from './entities/Region'; |
84 |
| - |
85 |
| -class MyEntitiesFactory { |
86 |
| - |
87 |
| - constructor() { |
88 |
| - this.map = { |
89 |
| - 'town': Town, |
90 |
| - 'country': Country, |
91 |
| - 'region': Region, |
92 |
| - }; |
93 |
| - } |
94 |
| - |
95 |
| - getClass(entityType) { |
96 |
| - return this.map[entityType]; |
97 |
| - } |
98 |
| - |
99 |
| - getModel(entityType) { |
100 |
| - var mappedClass = this.map[entityType]; |
101 |
| - |
102 |
| - if (!mappedClass) { |
103 |
| - throw new Error(`MyEntitiesFactory dont know about entity with type [${entityType}]`); |
104 |
| - } |
105 |
| - |
106 |
| - return new mappedClass(); |
107 |
| - } |
108 |
| -} |
109 |
| - |
110 |
| -export default MyEntitiesFactory; |
| 62 | +const newJson = dataFormatter.serialize({ |
| 63 | + stuff: model, |
| 64 | + includeNames: 'country' |
| 65 | +}); |
| 66 | +console.log(newJson); // will output: |
| 67 | +/* { |
| 68 | + data: { |
| 69 | + type: 'town', |
| 70 | + id: '123', |
| 71 | + attributes: { |
| 72 | + name: 'Barcelona', |
| 73 | + }, |
| 74 | + relationships: { |
| 75 | + country: { |
| 76 | + data: { |
| 77 | + type: 'country', |
| 78 | + id: '32', |
| 79 | + } |
| 80 | + } |
| 81 | + } |
| 82 | + }, |
| 83 | + included: [{ |
| 84 | + type: 'country', |
| 85 | + id: '32', |
| 86 | + attributes: { |
| 87 | + name: 'Spain', |
| 88 | + } |
| 89 | + }] |
| 90 | +}*/ |
111 | 91 | ```
|
112 | 92 |
|
113 |
| -**jsonaHelpers.js** |
114 |
| -```javascript |
115 |
| -import {Jsona} from 'jsona'; |
116 |
| -import MyEntitiesFactory from './MyEntitiesFactory'; |
117 |
| - |
118 |
| -/** |
119 |
| -* @var {MyEntitiesFactory} entitiesFactory - factory, that will using in Jsona for instantiate entities for each defined type |
120 |
| -*/ |
121 |
| -const entitiesFactory = new MyEntitiesFactory(); |
122 |
| -const dataFormatter = new Jsona(entitiesFactory); |
123 |
| - |
124 |
| -export function fromJsonToItem(jsonApiBody) { |
125 |
| - const deserialized = dataFormatter.deserialize(jsonApiBody); |
126 |
| - |
127 |
| - if (deserialized.hasItem) { |
128 |
| - return { |
129 |
| - item: deserialized.item, |
130 |
| - meta: deserialized.meta, |
131 |
| - }; |
132 |
| - } |
133 |
| - |
134 |
| - console.error('fromJsonToItem cant deserialize object', jsonApiBody); |
135 |
| - return {}; |
136 |
| -} |
137 |
| - |
138 |
| -export function fromJsonToCollection(jsonApiBody) { |
139 |
| - const deserialized = dataFormatter.deserialize(jsonApiBody); |
140 |
| - |
141 |
| - if (deserialized.hasCollection) { |
142 |
| - return { |
143 |
| - collection: deserialized.collection, |
144 |
| - meta: deserialized.meta, |
145 |
| - }; |
146 |
| - } |
147 |
| - |
148 |
| - console.error('fromJsonToCollection cant deserialize object', jsonApiBody); |
149 |
| - return {}; |
150 |
| -} |
151 |
| - |
152 |
| -export function fromCollectionToJson(collection, requestedIncludes, withAllIncludes = false) { |
153 |
| - return dataFormatter.serialize({collection, requestedIncludes, withAllIncludes}); |
154 |
| -} |
155 |
| - |
156 |
| -export function fromItemToJson(item, requestedIncludes, withAllIncludes = false) { |
157 |
| - return dataFormatter.serialize({item, requestedIncludes, withAllIncludes}); |
158 |
| -} |
159 |
| - |
160 |
| -export function fromJsonToItemOrCollection(jsonApiBody) { |
161 |
| - const deserialized = dataFormatter.deserialize(jsonApiBody); |
162 |
| - |
163 |
| - return { |
164 |
| - item: deserialized.item || null, |
165 |
| - collection: deserialized.collection || null, |
166 |
| - meta: deserialized.meta || null, |
167 |
| - }; |
168 |
| -} |
| 93 | +##### denormalizeReduxObject - creates simplified object(s) from reduxObject |
| 94 | +"reduxObject" - result object of [json-api-normalizer](https://github.com/yury-dymov/json-api-normalizer) |
169 | 95 |
|
170 |
| -export default { |
171 |
| - fromJsonToCollection, |
172 |
| - fromJsonToItem, |
173 |
| - fromCollectionToJson, |
174 |
| - fromItemToJson, |
175 |
| - fromJsonToItemOrCollection, |
176 |
| -}; |
177 |
| -``` |
178 |
| - |
179 |
| -**Town.js** (example of model) |
180 | 96 | ```javascript
|
181 |
| -import MyBaseEntity from './MyBaseEntity'; |
182 |
| -import Region from './Region'; |
183 |
| -import Country from './Country'; |
184 |
| - |
185 |
| -class Town extends MyBaseEntity { |
186 |
| - id: string; |
187 |
| - type: string; |
188 |
| - |
189 |
| - name: string; |
190 |
| - nameGenitive: string; |
191 |
| - timeZone: string; |
192 |
| - |
193 |
| - region: Region; |
194 |
| - country: Country; |
195 |
| - |
196 |
| - getRelationships() { |
197 |
| - return { |
198 |
| - region: this.region, |
199 |
| - country: this.country |
200 |
| - } |
201 |
| - } |
202 |
| -} |
203 |
| - |
204 |
| -export default Town; |
| 97 | +const reduxObject = reduxStore.entities; // depends on where you store it |
| 98 | +const model = dataFormatter.denormalizeReduxObject({reduxObject, 'town', '123'); |
| 99 | +console.log(newJson); // if there is such town and country in reduxObject, it will output: |
| 100 | +/* { |
| 101 | + type: 'town', |
| 102 | + id: '21', |
| 103 | + name: 'Shanghai', |
| 104 | + country: { |
| 105 | + type: 'country', |
| 106 | + id: '34', |
| 107 | + name: 'Spain' |
| 108 | + } |
| 109 | +} */ |
205 | 110 | ```
|
206 | 111 |
|
207 |
| -**MyBaseEntity.js** |
| 112 | +*NOTE:* You can control process of building this objects, just use your own [propertyMappers](https://github.com/olosegres/jsona/src/simplePropertyMappers.js) when Jsona instantiates. |
| 113 | +So, it may be easier to use, if you will create a proxy module in your project, something like this: |
208 | 114 | ```javascript
|
209 |
| -import {BaseJsonaModel} from 'jsona'; |
210 |
| - |
211 |
| -class MyBaseEntity extends BaseJsonaModel { |
212 |
| - constructor(props) { |
213 |
| - super(); |
214 |
| - |
215 |
| - Object.keys(params).forEach((k) => { |
216 |
| - this[k] = params[k]; |
217 |
| - }); |
218 |
| - } |
219 |
| -} |
| 115 | +import Jsona from 'jsona'; |
| 116 | +import {MyModelPropertiesMapper, MyJsonPropertiesMapper} from 'myPropertyMappers'; |
220 | 117 |
|
221 |
| -export default MyBaseEntity; |
| 118 | +export default const dataFormatter = new Jsona({ |
| 119 | + modelPropertiesMapper: MyModelPropertiesMapper, |
| 120 | + jsonPropertiesMapper: MyJsonPropertiesMapper |
| 121 | +}); |
222 | 122 | ```
|
223 | 123 |
|
224 | 124 | ### License
|
|
0 commit comments