Skip to content

Commit 68e06e6

Browse files
committed
fix: crash when data is null for a relationship
The relationship might be present for an entity but with data: null in case the relationships are not asked to be included but the API server has been implemented to return links to those resources.
1 parent 6ebc944 commit 68e06e6

File tree

3 files changed

+109
-14
lines changed

3 files changed

+109
-14
lines changed

src/__tests__/Deserializer.test.ts

+85-12
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { Deserializer, getDeserializer, Item, ItemDeserializer, RelationshipDese
22

33
const jsonapiOrgExampleData = {
44
links: {
5-
self: 'http://example.com/articles',
6-
next: 'http://example.com/articles?page[offset]=2',
7-
last: 'http://example.com/articles?page[offset]=10',
5+
self: 'https://example.com/articles',
6+
next: 'https://example.com/articles?page[offset]=2',
7+
last: 'https://example.com/articles?page[offset]=10',
88
},
99
data: [
1010
{
@@ -16,15 +16,15 @@ const jsonapiOrgExampleData = {
1616
relationships: {
1717
author: {
1818
links: {
19-
self: 'http://example.com/articles/1/relationships/author',
20-
related: 'http://example.com/articles/1/author',
19+
self: 'https://example.com/articles/1/relationships/author',
20+
related: 'https://example.com/articles/1/author',
2121
},
2222
data: { type: 'people', id: '9' },
2323
},
2424
comments: {
2525
links: {
26-
self: 'http://example.com/articles/1/relationships/comments',
27-
related: 'http://example.com/articles/1/comments',
26+
self: 'https://example.com/articles/1/relationships/comments',
27+
related: 'https://example.com/articles/1/comments',
2828
},
2929
data: [
3030
{ type: 'comments', id: '5' },
@@ -33,7 +33,7 @@ const jsonapiOrgExampleData = {
3333
},
3434
},
3535
links: {
36-
self: 'http://example.com/articles/1',
36+
self: 'https://example.com/articles/1',
3737
},
3838
},
3939
],
@@ -47,7 +47,7 @@ const jsonapiOrgExampleData = {
4747
twitter: 'dgeb',
4848
},
4949
links: {
50-
self: 'http://example.com/people/9',
50+
self: 'https://example.com/people/9',
5151
},
5252
},
5353
{
@@ -59,7 +59,7 @@ const jsonapiOrgExampleData = {
5959
twitter: 'jdoe',
6060
},
6161
links: {
62-
self: 'http://example.com/people/2',
62+
self: 'https://example.com/people/2',
6363
},
6464
},
6565
{
@@ -74,7 +74,7 @@ const jsonapiOrgExampleData = {
7474
},
7575
},
7676
links: {
77-
self: 'http://example.com/comments/5',
77+
self: 'https://example.com/comments/5',
7878
},
7979
},
8080
{
@@ -89,12 +89,49 @@ const jsonapiOrgExampleData = {
8989
},
9090
},
9191
links: {
92-
self: 'http://example.com/comments/12',
92+
self: 'https://example.com/comments/12',
9393
},
9494
},
9595
],
9696
};
9797

98+
// In this case we have requested just the articles without the included people and comments, but the links to the related resources are still there.
99+
const jsonapiOrgExampleData2 = {
100+
links: {
101+
self: 'https://example.com/articles',
102+
next: 'https://example.com/articles?page[offset]=2',
103+
last: 'https://example.com/articles?page[offset]=10',
104+
},
105+
data: [
106+
{
107+
type: 'articles',
108+
id: '1',
109+
attributes: {
110+
title: 'JSON:API paints my bikeshed!',
111+
},
112+
relationships: {
113+
author: {
114+
links: {
115+
self: 'https://example.com/articles/1/relationships/author',
116+
related: 'https://example.com/articles/1/author',
117+
},
118+
data: null,
119+
},
120+
comments: {
121+
links: {
122+
self: 'https://example.com/articles/1/relationships/comments',
123+
related: 'https://example.com/articles/1/comments',
124+
},
125+
data: null,
126+
},
127+
},
128+
links: {
129+
self: 'https://example.com/articles/1',
130+
},
131+
},
132+
],
133+
}
134+
98135
type Article = {
99136
id: number;
100137
title: string;
@@ -296,6 +333,26 @@ const fileSystemExampleData2 = {
296333
],
297334
};
298335

336+
// In this case we have requested just the root folder, so the children relationship is null but the relationships object is still present and populated with the links object.
337+
const fileSystemExampleData3 = {
338+
data: {
339+
type: 'folders',
340+
id: '1',
341+
attributes: {
342+
name: 'root',
343+
},
344+
relationships: {
345+
children: {
346+
links: {
347+
related: 'https://example.com/folders/1/children',
348+
self: 'https://example.com/folders/1/relationships/children'
349+
},
350+
data: null,
351+
},
352+
},
353+
}
354+
};
355+
299356
type Folder = {
300357
id: number;
301358
name: string;
@@ -341,6 +398,14 @@ describe('Deserializer', () => {
341398
expect(rootItems).toMatchSnapshot();
342399
});
343400

401+
it('deserializes the second jsonapi.org example (without relationships but with relationships.*.links and data:null) into an object graph', () => {
402+
const deserializer: Deserializer = getDeserializer([articleDeserializer, personDeserializer, commentDeserializer]);
403+
404+
const rootItems: any[] = deserializer.consume(jsonapiOrgExampleData2).getRootItems();
405+
406+
expect(rootItems).toMatchSnapshot();
407+
});
408+
344409
it('deserializes a file system example into an object graph', () => {
345410
const deserializer: Deserializer = getDeserializer([folderDeserializer, fileDeserializer]);
346411

@@ -356,4 +421,12 @@ describe('Deserializer', () => {
356421

357422
expect(rootItem).toMatchSnapshot();
358423
});
424+
425+
it('deserializes the third file system example (single entity with relationships.children.links) into an object graph', () => {
426+
const deserializer: Deserializer = getDeserializer([folderDeserializer, fileDeserializer]);
427+
428+
const rootItem: Folder = deserializer.consume(fileSystemExampleData3).getRootItem();
429+
430+
expect(rootItem).toMatchSnapshot();
431+
});
359432
});

src/__tests__/__snapshots__/Deserializer.test.ts.snap

+19
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,22 @@ exports[`Deserializer deserializes the second file system example (single entity
9494
"name": "root",
9595
}
9696
`;
97+
98+
exports[`Deserializer deserializes the second jsonapi.org example (without relationships but with relationships.*.links and data:null) into an object graph 1`] = `
99+
[
100+
{
101+
"author": null,
102+
"comments": [],
103+
"id": 1,
104+
"title": "JSON:API paints my bikeshed!",
105+
},
106+
]
107+
`;
108+
109+
exports[`Deserializer deserializes the third file system example (single entity with relationships.children.links) into an object graph 1`] = `
110+
{
111+
"children": [],
112+
"id": 1,
113+
"name": "root",
114+
}
115+
`;

src/index.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,10 @@ export class Deserializer implements RelationshipDeserializer {
9999
public deserializeRelationship(relationshipDeserializer: RelationshipDeserializer, item: Item, name: string): any {
100100
if (!item?.relationships || !item.relationships[name]) return null;
101101

102-
const relationship = item.relationships[name].data;
102+
const relationship = item.relationships[name]?.data;
103+
104+
if (!relationship) return null;
105+
103106
let relationshipItem: Item;
104107

105108
try {
@@ -125,7 +128,7 @@ export class Deserializer implements RelationshipDeserializer {
125128

126129
const ret: any[] = [];
127130

128-
item.relationships[name].data.forEach((relationship: any) => {
131+
item.relationships[name].data?.forEach((relationship: any) => {
129132
let relationshipItem: Item;
130133

131134
try {

0 commit comments

Comments
 (0)