UNPKG

@qoorp/jsona

Version:

Framework agnostic library that provide systemized way to work with JSON API specification v1.0 in your JavaScript / TypeScript code

235 lines (191 loc) 7.81 kB
import { TJsonaModel, TJsonApiBody, TJsonApiData, TJsonaDenormalizedIncludeNames, TJsonaNormalizedIncludeNamesTree, TJsonaUniqueIncluded, IModelPropertiesMapper } from '../JsonaTypes'; import {createIncludeNamesTree} from '../utils'; class ModelsSerializer { protected propertiesMapper: IModelPropertiesMapper; protected staff: TJsonaModel | Array<TJsonaModel>; protected includeNamesTree: TJsonaNormalizedIncludeNamesTree; constructor(propertiesMapper?: IModelPropertiesMapper) { propertiesMapper && this.setPropertiesMapper(propertiesMapper); } setPropertiesMapper(propertiesMapper: IModelPropertiesMapper) { this.propertiesMapper = propertiesMapper; } setStuff(staff) { this.staff = staff; } setIncludeNames(includeNames: TJsonaDenormalizedIncludeNames | TJsonaNormalizedIncludeNamesTree) { if (Array.isArray(includeNames)) { const includeNamesTree = {}; includeNames.forEach((namesChain) => { createIncludeNamesTree(namesChain, includeNamesTree); }); this.includeNamesTree = includeNamesTree; } else { this.includeNamesTree = includeNames; } } build(): TJsonApiBody { const {staff, propertiesMapper} = this; if (!propertiesMapper || typeof propertiesMapper !== 'object') { throw new Error('ModelsSerializer cannot build, propertiesMapper is not set'); } else if (!staff || typeof staff !== 'object') { throw new Error('ModelsSerializer cannot build, staff is not set'); } const body: TJsonApiBody = {}; const included: Array<TJsonApiData> = []; const uniqueIncluded: TJsonaUniqueIncluded = {}; if (staff && Array.isArray(staff)) { const collectionLength = staff.length; const data = []; for (let i = 0; i < collectionLength; i++) { data.push( this.buildDataByModel(staff[i]) ); this.buildIncludedByModel( staff[i], this.includeNamesTree, uniqueIncluded ); } body['data'] = data; } else if (staff) { body['data'] = this.buildDataByModel(staff); this.buildIncludedByModel( staff, this.includeNamesTree, uniqueIncluded ); } else if (staff === null) { body['data'] = null; } if (Object.keys(uniqueIncluded).length) { body['included'] = []; const includeUniqueKeys = Object.keys(uniqueIncluded).sort(); includeUniqueKeys.forEach((k) => { body['included'].push(uniqueIncluded[k]); }); } return body; } buildDataByModel(model: TJsonaModel | null): TJsonApiData { const data = { id: this.propertiesMapper.getId(model), type: this.propertiesMapper.getType(model), attributes: this.propertiesMapper.getAttributes(model), }; if (typeof data.type !== 'string' || !data.type) { console.warn('ModelsSerializer cannot buildDataByModel, type is not set or incorrect', model); throw new Error('ModelsSerializer cannot buildDataByModel, type is not set or incorrect'); } const relationships = this.buildRelationshipsByModel(model); if (relationships && Object.keys(relationships).length) { data['relationships'] = relationships; } return data; } buildRelationshipsByModel(model: TJsonaModel) { const relations = this.propertiesMapper.getRelationships(model); if (!relations || !Object.keys(relations).length) { return; } const relationships = {}; Object.keys(relations).forEach((k) => { const relation = relations[k]; if (Array.isArray(relation)) { const relationshipData = []; const relationLength = relation.length; for (let i = 0; i < relationLength; i++) { const item = { id: this.propertiesMapper.getId(relation[i]), type: this.propertiesMapper.getType(relation[i]) }; if (item.id && item.type) { relationshipData.push(item); } else { console.error( `Can't create data item[${i}] for relationship ${k}, it doesn't have 'id' or 'type', it was skipped`, relation[i] ); } } relationships[k] = { data: relationshipData }; } else if (relation) { const item = { id: this.propertiesMapper.getId(relation), type: this.propertiesMapper.getType(relation) }; if (item.type) { relationships[k] = { data: item }; } else { console.error( `Can't create data for relationship ${k}, it doesn't have 'type', it was skipped`, relation ); } } else { relationships[k] = { data: relation }; } }); return relationships; } buildIncludedByModel( model: TJsonaModel, includeTree: TJsonaNormalizedIncludeNamesTree, builtIncluded: TJsonaUniqueIncluded = {} ): void { if (!includeTree || !Object.keys(includeTree).length) { return; } const modelRelationships = this.propertiesMapper.getRelationships(model); if (!modelRelationships || !Object.keys(modelRelationships).length) { return; } const includeNames = Object.keys(includeTree); const includeNamesLength = includeNames.length; for (let i = 0; i < includeNamesLength; i++) { const currentRelationName = includeNames[i]; const relation: TJsonaModel | Array<TJsonaModel> = modelRelationships[currentRelationName]; if (relation) { if (Array.isArray(relation)) { let relationModelsLength = relation.length; for (let r = 0; r < relationModelsLength; r++) { const relationModel: TJsonaModel = relation[r]; this.buildIncludedItem(relationModel, includeTree[currentRelationName], builtIncluded); } } else { this.buildIncludedItem(relation, includeTree[currentRelationName], builtIncluded); } } } } buildIncludedItem( relationModel: TJsonaModel, subIncludeTree: TJsonaNormalizedIncludeNamesTree, builtIncluded: TJsonaUniqueIncluded ) { const includeKey = this.propertiesMapper.getType(relationModel) + this.propertiesMapper.getId(relationModel); if (!builtIncluded[includeKey]) { // create data by current entity if such included is not yet created builtIncluded[includeKey] = this.buildDataByModel(relationModel); if (subIncludeTree) { this.buildIncludedByModel(relationModel, subIncludeTree, builtIncluded); } } } } export default ModelsSerializer;