/** * @category Services/utils * Utilitaire aidant à manipuler des Objets */ import _ from 'lodash' import type { AnyJson } from '~/types/data' import StringUtils from '~/services/utils/stringUtils' const ObjectUtils = { /** * Flatten un objet nested en un objet avec un seul niveau avec des noms de propriétés transformées comme cela 'foo.bar' * L'objet passé en paramètre reste inchangé car il est cloné * * @example cloneAndFlatten({ a: 1, b: { c: 2 }, d: { e: 3, f: { g: 4, h: 5 } }, i: { j: 6 } }, ['i']) => { a: 1, 'b.c': 2, 'd.e': 3, 'd.f.g': 4, 'd.f.h': 5, i: { j: 6 } } } * @param {AnyJson} object * @param {Array} excludedProperties * @param excludedProperties * @return {AnyJson} */ cloneAndFlatten( object: AnyJson, excludedProperties: Array = [], ): AnyJson { if (typeof object !== 'object') { throw new TypeError('Expecting an object parameter') } return Object.keys(object).reduce((values: AnyJson, name: string) => { if (!Object.prototype.hasOwnProperty.call(object, name)) { return values } if (this.isObject(object[name])) { if (!excludedProperties.includes(name)) { const flatObject = this.cloneAndFlatten(object[name]) Object.keys(flatObject).forEach((flatObjectKey) => { if ( !Object.prototype.hasOwnProperty.call(flatObject, flatObjectKey) ) { return } values[name + '.' + flatObjectKey] = flatObject[flatObjectKey] }) } else { values[name] = this.clone(object[name]) } } else { values[name] = object[name] } return values }, {}) }, /** * Transforme un objet flattened en un objet nested. L'objet passé en paramètre reste inchangé * * @example cloneAndNest({ a: 1, 'b.c': 2, 'd.e': 3, 'd.f.g': 4, 'd.f.h': 5 } ) => { a: 1, b: { c: 2 }, d: { e: 3, f: { g: 4, h: 5 } } } * @param {AnyJson} object * @return {AnyJson} */ cloneAndNest(object: AnyJson): AnyJson { if (typeof object !== 'object') { throw new TypeError('Expecting an object parameter') } return Object.keys(object).reduce((values, name) => { if (!Object.prototype.hasOwnProperty.call(object, name)) { return values } name .split('.') .reduce( ( previous: AnyJson, current: string, index: number, list: Array, ) => { if (previous != null) { if (typeof previous[current] === 'undefined') { previous[current] = {} } if (index < list.length - 1) { return previous[current] } previous[current] = object[name] } return null }, values, ) return values }, {}) }, /** * Teste si le paramètre est un objet * * @param {AnyJson} value * @return {boolean} */ isObject(value: unknown): boolean { return ( value !== null && typeof value === 'object' && !Array.isArray(value) && Object.prototype.toString.call(value) !== '[object Date]' ) }, /** * Clone l'objet et ses propriétés. * * @param {ObjectUtils} object * @return {ObjectUtils} */ clone(object: AnyJson): AnyJson { return Object.keys(object).reduce((values: AnyJson, name: string) => { if (Object.prototype.hasOwnProperty.call(object, name)) { values[name] = object[name] } return values }, {}) }, /** * Trie un objet selon ses clés (par ordre alphanumérique) * * @example sortObjectsByKey({b:1, d:2, c:3, a:4}) => {a:4, b:1, c:3, d:2} * @param toSort */ sortObjectsByKey(toSort: AnyJson): AnyJson { if (typeof toSort !== 'object') { throw new TypeError('Expecting an object parameter') } return Object.keys(toSort) .sort() .reduce((obj: AnyJson, key: string) => { obj[key] = toSort[key] return obj }, {}) }, /** * Créé un hash à partir d'un objet * (après l'avoir trié selon ses clés, et converti en json sans espace) * * @param obj */ async hash(obj: object): Promise { const sortedObject = this.sortObjectsByKey(_.cloneDeep(obj)) return await StringUtils.hash(JSON.stringify(sortedObject), 'SHA-1') } } export default ObjectUtils