Skip to content

Commit 2d2ec38

Browse files
committed
#55 Fixes only the first element of array is included in the resulting body if elements don't have id
1 parent 6549a51 commit 2d2ec38

10 files changed

+88
-55
lines changed

package-lock.json

+20-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,17 @@
4040
"files": [
4141
"lib"
4242
],
43-
"dependencies": {},
43+
"dependencies": {
44+
"tslib": "^2.4.1"
45+
},
4446
"devDependencies": {
4547
"@types/chai": "^4.2.4",
4648
"@types/mocha": "^5.2.7",
4749
"@types/node": "^12.11.5",
4850
"chai": "^4.3.4",
4951
"mocha": "^10.1.0",
5052
"ts-mocha": "^8.0.0",
51-
"typescript": "^4.2.4"
53+
"typescript": "^4.9.3"
5254
},
5355
"scripts": {
5456
"clean": "rm -rf ./lib/*",

src/Jsona.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ class Jsona {
6060
* serialize
6161
* Creates JSON, compatible with json:api specification from Jsona model(s).
6262
*/
63-
serialize(
64-
{stuff, includeNames}: {
63+
serialize({
64+
stuff, includeNames}: {
6565
stuff: TJsonaModel | Array<TJsonaModel>,
6666
includeNames?: TJsonaDenormalizedIncludeNames | TJsonaNormalizedIncludeNamesTree
6767
}

src/JsonaTypes.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface IJsonPropertiesMapper {
1313
setAttributes(model: TJsonaModel, attributes: TAnyKeyValueObject): void;
1414
setMeta(model: TJsonaModel, meta: TAnyKeyValueObject): void;
1515
setLinks(model: TJsonaModel, links: TAnyKeyValueObject): void;
16-
setResourceIdObjMeta(model: TJsonaModel, meta: TResourceIdObj): void;
16+
setResourceIdObjMeta(model: TJsonaModel, meta: unknown): void;
1717
setRelationships(model: TJsonaModel, relationships: TJsonaRelationships): void;
1818
setRelationshipLinks(parentModel: TJsonaModel, relationName: string, links: TJsonApiLinks): void;
1919
setRelationshipMeta(parentModel: TJsonaModel, relationName: string, meta: TAnyKeyValueObject): void;
@@ -157,10 +157,8 @@ export type TJsonaModel = {
157157

158158
export type TResourceIdObj = {
159159
id?: string | number,
160-
type?: string,
161-
meta?: {
162-
[propertyName: string]: any
163-
},
160+
type: string,
161+
meta?: unknown,
164162
[propertyName: string]: any
165163
};
166164

src/builders/JsonDeserializer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class JsonDeserializer implements IJsonaDeserializer {
6666
return stuff;
6767
}
6868

69-
buildModelByData(data: TJsonApiData, resourceIdObj: TResourceIdObj = {}): TJsonaModel {
69+
buildModelByData(data: TJsonApiData, resourceIdObj?: TResourceIdObj): TJsonaModel {
7070
const cachedModel = this.dc.getCachedModel(data, resourceIdObj);
7171

7272
if (cachedModel) {
@@ -92,7 +92,7 @@ export class JsonDeserializer implements IJsonaDeserializer {
9292
this.pm.setLinks(model, data.links);
9393
}
9494

95-
if (resourceIdObj.meta) {
95+
if (resourceIdObj?.meta) {
9696
this.pm.setResourceIdObjMeta(model, resourceIdObj.meta);
9797
}
9898

src/builders/ModelsSerializer.ts

+35-26
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ export class ModelsSerializer implements IModelsSerializer {
1616
protected propertiesMapper: IModelPropertiesMapper;
1717
protected stuff: TJsonaModel | Array<TJsonaModel>;
1818
protected includeNamesTree: TJsonaNormalizedIncludeNamesTree;
19+
private buildIncludedIndex: number;
1920

2021
constructor(propertiesMapper?: IModelPropertiesMapper) {
2122
propertiesMapper && this.setPropertiesMapper(propertiesMapper);
23+
this.buildIncludedIndex = 0;
2224
}
2325

2426
setPropertiesMapper(propertiesMapper: IModelPropertiesMapper) {
@@ -84,21 +86,19 @@ export class ModelsSerializer implements IModelsSerializer {
8486
}
8587

8688
if (Object.keys(uniqueIncluded).length) {
87-
body['included'] = [];
88-
const includeUniqueKeys = Object.keys(uniqueIncluded);
89-
includeUniqueKeys.forEach((k) => {
90-
body['included'].push(uniqueIncluded[k]);
91-
});
89+
body['included'] = Object.values(uniqueIncluded);
9290
}
9391

9492
return body;
9593
}
9694

9795
buildDataByModel(model: TJsonaModel | null): TJsonApiData {
98-
const data = {
99-
id: this.propertiesMapper.getId(model),
100-
type: this.propertiesMapper.getType(model),
101-
attributes: this.propertiesMapper.getAttributes(model),
96+
const id = this.propertiesMapper.getId(model);
97+
const type = this.propertiesMapper.getType(model);
98+
const attributes = this.propertiesMapper.getAttributes(model);
99+
const data = { type,
100+
...(typeof id !== 'undefined' ? { id } : {}),
101+
...(typeof attributes !== 'undefined' ? { attributes } : {}),
102102
};
103103

104104
if (typeof data.type !== 'string' || !data.type) {
@@ -115,6 +115,16 @@ export class ModelsSerializer implements IModelsSerializer {
115115
return data;
116116
}
117117

118+
buildResourceObjectPart(relation: TJsonaModel) {
119+
const id = this.propertiesMapper.getId(relation);
120+
const type = this.propertiesMapper.getType(relation);
121+
122+
return {
123+
type,
124+
...(typeof id === 'undefined' ? {} : { id }),
125+
};
126+
}
127+
118128
buildRelationshipsByModel(model: TJsonaModel) {
119129
const relations = this.propertiesMapper.getRelationships(model);
120130

@@ -129,21 +139,17 @@ export class ModelsSerializer implements IModelsSerializer {
129139

130140
if (Array.isArray(relation)) {
131141
const relationshipData = [];
132-
const relationLength = relation.length;
133142

134-
for (let i = 0; i < relationLength; i++) {
135-
const item = {
136-
id: this.propertiesMapper.getId(relation[i]),
137-
type: this.propertiesMapper.getType(relation[i])
138-
};
143+
for (const relationItem of relation) {
144+
const relationshipDataItem = this.buildResourceObjectPart(relationItem);
139145

140-
if (item.id && item.type) {
141-
relationshipData.push(item);
146+
if ('type' in relationshipDataItem) {
147+
relationshipData.push(relationshipDataItem);
142148
} else {
143149
console.error(
144-
`Can't create data item[${i}] for relationship ${k},
150+
`Can't create data item for relationship ${k},
145151
it doesn't have 'id' or 'type', it was skipped`,
146-
relation[i]
152+
relationItem
147153
);
148154
}
149155
}
@@ -152,12 +158,9 @@ export class ModelsSerializer implements IModelsSerializer {
152158
data: relationshipData
153159
};
154160
} else if (relation) {
155-
const item = {
156-
id: this.propertiesMapper.getId(relation),
157-
type: this.propertiesMapper.getType(relation)
158-
};
161+
const item = this.buildResourceObjectPart(relation);
159162

160-
if (item.type) {
163+
if ('type' in item) {
161164
relationships[k] = {
162165
data: item
163166
};
@@ -218,10 +221,16 @@ export class ModelsSerializer implements IModelsSerializer {
218221
subIncludeTree: TJsonaNormalizedIncludeNamesTree,
219222
builtIncluded: TJsonaUniqueIncluded
220223
) {
221-
const includeKey = this.propertiesMapper.getType(relationModel) + this.propertiesMapper.getId(relationModel);
224+
const id = this.propertiesMapper.getId(relationModel);
225+
const type = this.propertiesMapper.getType(relationModel);
226+
let includeKey = type + id;
222227

223-
if (!builtIncluded[includeKey]) {
228+
if (!id || !builtIncluded[includeKey]) {
224229
// create data by current entity if such included is not yet created
230+
if (includeKey in builtIncluded) {
231+
includeKey += this.buildIncludedIndex;
232+
this.buildIncludedIndex += 1;
233+
}
225234
builtIncluded[includeKey] = this.buildDataByModel(relationModel);
226235

227236
if (subIncludeTree) {

src/cache.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
IDeserializeCache, TAnyKeyValueObject
2+
IDeserializeCache, TAnyKeyValueObject, TJsonaModel, TJsonApiData, TResourceIdObj
33
} from './JsonaTypes';
44

55

@@ -20,12 +20,12 @@ export class DeserializeCache implements IDeserializeCache {
2020

2121
protected cachedModels = {};
2222

23-
getCachedModel(data, resourceIdObject) {
23+
getCachedModel(data:TJsonApiData, resourceIdObject: TResourceIdObj) {
2424
const entityKey = this.createCacheKey(data, resourceIdObject);
2525
return this.cachedModels[entityKey] || null;
2626
}
2727

28-
handleModel(model, data, resourceIdObject) {
28+
handleModel(model: TJsonaModel, data: TJsonApiData, resourceIdObject: TResourceIdObj) {
2929
const entityKey = this.createCacheKey(data, resourceIdObject);
3030
const dataWithPayload = data.attributes || data.relationships;
3131

@@ -34,16 +34,16 @@ export class DeserializeCache implements IDeserializeCache {
3434
}
3535
}
3636

37-
createCacheKey(data, resourceIdObject) {
37+
createCacheKey(data, resourceIdObject?: TResourceIdObj) {
3838
// resourceIdObject.meta sets to model in simplePropertyMappers.ts, so it should be used here too
3939
// cache in this case probably will be redundant
4040
if (!data.id || !data.type) {
4141
return;
4242
}
4343

44-
let resourcePart = `${resourceIdObject.type}-${resourceIdObject.id}`;
44+
let resourcePart = resourceIdObject ? `${resourceIdObject.type}-${resourceIdObject.id}` : '';
4545

46-
if (resourceIdObject.meta) {
46+
if (resourceIdObject?.meta) {
4747
resourcePart += `-${jsonStringify(resourceIdObject.meta)}`;
4848
}
4949

tests/Jsona.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('Jsona', () => {
5555
});
5656

5757
it('should build json with collection, with included resources that do not have ids', () => {
58-
const jsonBody = jsona.serialize({stuff: userWithIdlessSpecialties.model, includeNames: ['specialty']});
58+
const jsonBody = jsona.serialize({stuff: userWithIdlessSpecialties.model, includeNames: ['specialtyWithoutIds']});
5959
expect(jsonBody.included).to.be.deep.equal([idlessSpecialty1.json, idlessSpecialty2.json]);
6060
});
6161

tests/mocks.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ export const userWithIdlessSpecialties = {
244244
id: '2',
245245
name: 'myName2',
246246
active: true,
247-
specialty: [idlessSpecialty1.model, idlessSpecialty2.model],
248-
[RELATIONSHIP_NAMES_PROP]: ['specialty']
247+
specialtyWithoutIds: [idlessSpecialty1.model, idlessSpecialty2.model],
248+
[RELATIONSHIP_NAMES_PROP]: ['specialtyWithoutIds']
249249
},
250250
};
251251

tsconfig.json

+13-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,20 @@
55
"module": "commonjs",
66
"declaration": true,
77
"sourceMap": true,
8-
"outDir": "lib/"
8+
"outDir": "lib/",
9+
"strict": false,
10+
"removeComments": true,
11+
"allowSyntheticDefaultImports": true,
12+
"moduleResolution": "node",
13+
"importHelpers": true,
14+
"skipLibCheck": true
915
},
1016
"files": [
1117
"./src/index.ts"
12-
]
18+
],
19+
"lib": [
20+
"es6",
21+
"dom"
22+
],
23+
"include": ["./src"]
1324
}

0 commit comments

Comments
 (0)