packages/ros-cdk-cli/lib/util/objects.ts (98 lines of code) (raw):

import { isArray, isObject, Obj } from './types'; /** * Return a new object by adding missing keys into another object */ export function applyDefaults(hash: any, defaults: any) { const result: any = {}; Object.keys(hash).forEach((k) => (result[k] = hash[k])); Object.keys(defaults) .filter((k) => !(k in result)) .forEach((k) => (result[k] = defaults[k])); return result; } /** * Return whether the given parameter is an empty object or empty list. */ export function isEmpty(x: any) { if (x == null) { return false; } if (isArray(x)) { return x.length === 0; } return Object.keys(x).length === 0; } /** * Deep clone a tree of objects, lists or scalars * * Does not support cycles. */ export function deepClone(x: any): any { if (typeof x === 'undefined') { return undefined; } if (x === null) { return null; } if (isArray(x)) { return x.map(deepClone); } if (isObject(x)) { return makeObject(mapObject(x, (k, v) => [k, deepClone(v)] as [string, any])); } return x; } /** * Map over an object, treating it as a dictionary */ export function mapObject<T, U>(x: Obj<T>, fn: (key: string, value: T) => U): U[] { const ret: U[] = []; Object.keys(x).forEach((key) => { ret.push(fn(key, x[key])); }); return ret; } /** * Construct an object from a list of (k, v) pairs */ export function makeObject<T>(pairs: Array<[string, T]>): Obj<T> { const ret: Obj<T> = {}; for (const pair of pairs) { ret[pair[0]] = pair[1]; } return ret; } /** * Deep get a value from a tree of nested objects * * Returns undefined if any part of the path was unset or * not an object. */ export function deepGet(x: any, path: string[]): any { path = path.slice(); while (path.length > 0 && isObject(x)) { const key = path.shift()!; x = x[key]; } return path.length === 0 ? x : undefined; } /** * Deep set a value in a tree of nested objects * * Throws an error if any part of the path is not an object. */ export function deepSet(x: any, path: string[], value: any) { path = path.slice(); if (path.length === 0) { throw new Error('Path may not be empty'); } while (path.length > 1 && isObject(x)) { const key = path.shift()!; if (!(key in x)) { x[key] = {}; } x = x[key]; } if (!isObject(x)) { throw new Error(`Expected an object, got '${x}'`); } if (value !== undefined) { x[path[0]] = value; } else { delete x[path[0]]; } } /** * Recursively merge objects together * * The leftmost object is mutated and returned. Arrays are not merged * but overwritten just like scalars. * * If an object is merged into a non-object, the non-object is lost. */ export function deepMerge(...objects: Array<Obj<any> | undefined>) { function mergeOne(target: Obj<any>, source: Obj<any>) { for (const key of Object.keys(source)) { const value = source[key]; if (isObject(value)) { if (!isObject(target[key])) { target[key] = {}; } // Overwrite on purpose mergeOne(target[key], value); } else if (typeof value !== 'undefined') { target[key] = value; } } } const others = objects.filter((x) => x != null) as Array<Obj<any>>; if (others.length === 0) { return {}; } const into = others.splice(0, 1)[0]; others.forEach((other) => mergeOne(into, other)); return into; }