json-api-deserialize
Version:
A deserializer for JSON:API responses
119 lines (99 loc) • 4.19 kB
text/typescript
import { get, set } from 'lodash';
import { asArray } from '../../utils'
import { normalize } from '../normalizer/normalizer.func';
import IDocument from '../../meta/document.interface';
import IResource from '../../meta/resource.interface';
import IResourceIdentifier from '../../meta/resource-identifier.interface';
import IDeserialized from '../../meta/deserialized.interface';
import IDeserializeOptions from 'src/meta/deserialize-options.interface';
class Deserializer {
private response: IDocument;
private deserialized: IDeserialized;
public getDeserialized(
response: IDocument,
options: IDeserializeOptions = { normalize: true },
): IDeserialized | IDocument {
this.response = response; // Keep an instance copy of the raw response
this.deserialized = response as IDeserialized; // The object to modify that will get returned
try {
if (!response || !response.included) {
return this.getReturnObject(true, options);
}
const resourceAsArray: IResource[] = asArray(response.data);
for (const [index, resource] of resourceAsArray.entries()) {
this.mapResources(resource, index);
}
return this.getReturnObject(true, options);
} catch (err) {
return this.response;
}
}
private mapResources(resource: IResource, indexInCollection?: number, nestedObjectPath?: string) {
for (const resourceRelationshipKey in resource.relationships) {
const resourceIdentifiers: IResourceIdentifier[] = asArray(resource.relationships[resourceRelationshipKey].data);
for (const resourceIdentifier of resourceIdentifiers) {
if (resourceIdentifier && resourceIdentifier.id && resourceIdentifier.type) {
const includedResourceObject = this.response.included.find((included) => {
return included.id === resourceIdentifier.id
&& included.type === resourceIdentifier.type;
});
if (includedResourceObject) {
if (Array.isArray(this.response.data)) {
this.setNestedResourceOnIdentifierObject(
nestedObjectPath
? nestedObjectPath + `.relationships[${resourceRelationshipKey}].data`
: null
|| `data[${indexInCollection}]relationships[${resourceRelationshipKey}].data`,
includedResourceObject,
indexInCollection,
);
} else {
this.setNestedResourceOnIdentifierObject(
nestedObjectPath
? nestedObjectPath + `.relationships[${resourceRelationshipKey}].data`
: null
|| `data.relationships[${resourceRelationshipKey}].data`,
includedResourceObject,
);
}
}
}
}
}
}
private setNestedResourceOnIdentifierObject(
identifierObjectPath: string,
includedResourceObject: IResource,
indexInCollection?: number,
) {
const resourceIdObject: IResourceIdentifier = get(this.response, identifierObjectPath);
let fullPath: string = identifierObjectPath;
if (resourceIdObject) {
if (Array.isArray(resourceIdObject)) {
const resourceIdObjectIndex: number = resourceIdObject.findIndex((identifierObject) => {
return identifierObject.id === includedResourceObject.id
&& identifierObject.type === includedResourceObject.type;
});
fullPath = fullPath + `[${resourceIdObjectIndex}]`;
set(this.deserialized, fullPath, includedResourceObject);
} else {
set(this.deserialized, fullPath, includedResourceObject);
}
}
if (includedResourceObject.relationships) {
this.mapResources(includedResourceObject, indexInCollection, fullPath);
}
}
private getReturnObject(wasDeserialized: boolean, options: IDeserializeOptions) {
this.deserialized.deserialized = wasDeserialized;
if (wasDeserialized) {
delete this.deserialized.included;
}
if (wasDeserialized && options.normalize) {
return normalize(this.deserialized);
} else {
return this.deserialized;
}
}
}
export default new Deserializer();