@winglet/json
Version:
TypeScript library for safe and efficient JSON data manipulation with RFC 6901 (JSON Pointer) and RFC 6902 (JSON Patch) compliance, featuring prototype pollution protection and immutable operations
97 lines (94 loc) • 3.96 kB
JavaScript
import { isArray, isObject } from '@winglet/common-utils/filter';
import { getKeys, hasOwnProperty } from '@winglet/common-utils/lib';
import { JSONPointer } from '../../../enum.mjs';
import { escapeSegment } from '../../escape/escapeSegment.mjs';
import { Operation } from '../type.mjs';
import { processValue } from './utils/processValue.mjs';
const compareRecursive = (source, target, patches, path, strict, immutable) => {
if (source === target || (source !== source && target !== target))
return;
if ('toJson' in source && typeof source.toJson === 'function')
source = source.toJson();
if ('toJson' in target && typeof target.toJson === 'function')
target = target.toJson();
const sourceKeys = getKeys(source);
const targetKeys = getKeys(target);
const sourceIsArray = isArray(source);
const targetIsArray = isArray(target);
if (sourceIsArray !== targetIsArray) {
if (strict) {
patches.push({ op: Operation.TEST, path, value: source });
}
patches.push({ op: Operation.REPLACE, path, value: target });
return;
}
let hasRemoved = false;
for (let i = 0, l = sourceKeys.length; i < l; i++) {
const key = sourceKeys[i];
const sourceValue = source[key];
if (hasOwnProperty(target, key)) {
const targetValue = target[key];
if (sourceValue === targetValue ||
(sourceValue !== sourceValue && targetValue !== targetValue))
continue;
if (targetValue === undefined &&
sourceValue !== undefined &&
!targetIsArray) {
const targetPath = path + JSONPointer.Separator + escapeSegment(key);
if (strict) {
patches.push({
op: Operation.TEST,
path: targetPath,
value: processValue(sourceValue, immutable),
});
}
patches.push({ op: Operation.REMOVE, path: targetPath });
hasRemoved = true;
continue;
}
if ((isObject(sourceValue) && isObject(targetValue)) ||
(isArray(sourceValue) && isArray(targetValue))) {
compareRecursive(sourceValue, targetValue, patches, path + JSONPointer.Separator + escapeSegment(key), strict, immutable);
}
else {
const targetPath = path + JSONPointer.Separator + escapeSegment(key);
if (strict)
patches.push({
op: Operation.TEST,
path: targetPath,
value: processValue(sourceValue, immutable),
});
patches.push({
op: Operation.REPLACE,
path: targetPath,
value: processValue(targetValue, immutable),
});
}
}
else {
const targetPath = path + JSONPointer.Separator + escapeSegment(key);
if (strict)
patches.push({
op: Operation.TEST,
path: targetPath,
value: processValue(sourceValue, immutable),
});
patches.push({ op: Operation.REMOVE, path: targetPath });
hasRemoved = true;
}
}
if (!hasRemoved && targetKeys.length === sourceKeys.length)
return;
for (let i = 0, l = targetKeys.length; i < l; i++) {
const key = targetKeys[i];
const targetValue = target[key];
if (hasOwnProperty(source, key) || targetValue === undefined)
continue;
patches.push({
op: Operation.ADD,
path: path + JSONPointer.Separator + escapeSegment(key),
value: processValue(targetValue, immutable),
});
}
};
export { compareRecursive };