import { JSONObject, JSONValue } from "./validate"; import * as log from "./log"; /** * patches _old to deep equal _new * needed so objects can be updated without breaking references to them * * @param oldObject object to be updated * @param newObject object with values to use for update * @param path path to the object to be updated */ export function patchInPlace( oldObject: JSONObject, newObject: JSONObject, path = "" ): void { if (typeof oldObject !== "object") { log.error("_old is not an object"); return; } if (typeof newObject !== "object") { log.error("_new is not an object"); return; } const oldKeys = Object.keys(oldObject).reverse(); const newKeys = Object.keys(newObject); // remove oldObject keys that are not in newObject for (const key of oldKeys) { if (!Object.prototype.hasOwnProperty.call(newObject, key)) { // log.debug(`remove ${_keyPath}.${key}`); if (Array.isArray(oldObject)) { oldObject.splice(parseInt(key), 1); } else { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete oldObject[key]; } } } // add newObject keys that are not in oldObject for (const key of newKeys) { if (!Object.prototype.hasOwnProperty.call(oldObject, key)) { // log.debug(`add ${_keyPath}.${key}`); oldObject[key] = newObject[key]; } } // patch shared object and array keys for (const key of newKeys) { if (Object.prototype.hasOwnProperty.call(oldObject, key)) { const oldType = getMergeType(oldObject[key]); const newType = getMergeType(newObject[key]); // bail if type is unsupported if (newType === "unsupported") { log.error( `${path}.${key} is unsupported type: ${typeof newObject[key]}` ); continue; } // merge objects if (oldType === "object" && newType === "object") { // casts are safe; objects in JSONObjects are always JSON Objects patchInPlace( oldObject[key] as JSONObject, newObject[key] as JSONObject, `${path}.${key}` ); continue; } // merge arrays if (oldType === "array" && newType === "array") { // casts are safe; arrays in JSONObjects can be treated by // this function as JSONObjects patchInPlace( oldObject[key] as JSONObject, newObject[key] as JSONObject, `${path}.${key}` ); continue; } // replace everything else if (oldObject[key] !== newObject[key]) { oldObject[key] = newObject[key]; } } } } // module.exports = { patchInPlace }; function getMergeType(value: JSONValue): string { if (value === null) return "null"; if (Array.isArray(value)) return "array"; if (typeof value === "object") return "object"; if (typeof value === "boolean") return "primitive"; if (typeof value === "number" && Number.isFinite(value)) return "primitive"; if (typeof value === "string") return "primitive"; return "unsupported"; }