|
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