vanilla-party
Version:
An easy to use library for simple multi-user JavaScript apps.
106 lines (94 loc) • 3.06 kB
text/typescript
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"
}