UNPKG

@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

73 lines (70 loc) 3.55 kB
import { isArray } from '@winglet/common-utils/filter'; import { JSONPointer } from '../../../enum.mjs'; import { unescapePath } from '../../escape/unescapePath.mjs'; import { JsonPatchError } from './utils/error.mjs'; import { getArrayIndex } from './utils/getArrayIndex.mjs'; import { handleArray } from './utils/handleArray.mjs'; import { handleObject } from './utils/handleObject.mjs'; import { handleRootPatch } from './utils/handleRootPatch.mjs'; import { isPrototypeModification } from './utils/isPrototypeModification.mjs'; const applySinglePatch = (source, patch, patchIndex, strict, protectPrototype) => { if (patch.path === '' || patch.path === JSONPointer.Fragment) return handleRootPatch(source, patch, patchIndex, strict); const segments = patch.path.split('/'); let current = source; let cursor = 1; const segmentsLength = segments.length; while (cursor < segmentsLength) { let segment = unescapePath(segments[cursor]); if (protectPrototype && isPrototypeModification(segment, segments, cursor)) throw new JsonPatchError('SECURITY_PROTOTYPE_MODIFICATION_FORBIDDEN', 'Modifying prototype properties (__proto__, constructor.prototype) is forbidden for security reasons', { patch, index: patchIndex, segment, path: segments.slice(0, cursor + 1).join('/'), operation: patch.op, }); if (cursor === segmentsLength - 1) { if (isArray(current)) { const arrayIndex = getArrayIndex(segment, current); return handleArray(patch, current, arrayIndex, source, patchIndex, strict); } else if (current && typeof current === 'object') { return handleObject(patch, current, segment, source, patchIndex, strict); } else { throw new JsonPatchError('PATCH_TARGET_NOT_OBJECT', `Cannot apply ${patch.op} operation to non-object value. Target path points to: ${typeof current}`, { patch, patchIndex: patchIndex, targetValue: current, targetType: typeof current, path: patch.path, operation: patch.op, }); } } if (isArray(current)) segment = getArrayIndex(segment, current); current = current[segment]; if (!current || typeof current !== 'object') { throw new JsonPatchError('PATCH_PATH_INVALID_INTERMEDIATE', `Cannot traverse path '${patch.path}' - intermediate value at '${segments.slice(0, cursor + 1).join('/')}' is ${current === null ? 'null' : current === undefined ? 'undefined' : typeof current}, expected object or array`, { patch, patchIndex: patchIndex, intermediateValue: current, intermediateType: current === null ? 'null' : typeof current, failedAtPath: segments.slice(0, cursor + 1).join('/'), remainingPath: segments.slice(cursor + 1).join('/'), operation: patch.op, }); } cursor++; } throw new JsonPatchError('PATCH_PATH_PROCESSING_ERROR', 'Unexpected error while processing patch path - this should not happen', { patch, patchIndex: patchIndex, operation: patch.op, processedSegments: cursor, totalSegments: segmentsLength, }); }; export { applySinglePatch };