UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

114 lines (113 loc) 3.96 kB
import { deepEqual } from '@jsonjoy.com/util/lib/json-equal/deepEqual'; import * as str from '../util/diff/str'; import * as line from '../util/diff/line'; import { structHash } from '../json-hash'; export class JsonPatchDiff { patch = []; diffVal(path, src, dst) { if (deepEqual(src, dst)) return; this.patch.push({ op: 'replace', path, value: dst }); } diffStr(path, src, dst) { if (src === dst) return; const patch = this.patch; str.apply(str.diff(src, dst), src.length, (pos, str) => patch.push({ op: 'str_ins', path, pos, str }), (pos, len, str) => patch.push({ op: 'str_del', path, pos, len, str })); } diffBin(path, src, dst) { throw new Error('Not implemented'); } diffObj(path, src, dst) { const patch = this.patch; for (const key in src) { if (key in dst) { const val1 = src[key]; const val2 = dst[key]; if (val1 === val2) continue; this.diffAny(path + '/' + key, val1, val2); } else { patch.push({ op: 'remove', path: path + '/' + key }); } } for (const key in dst) { if (key in src) continue; patch.push({ op: 'add', path: path + '/' + key, value: dst[key] }); } } diffArr(path, src, dst) { const srcLines = []; const dstLines = []; const srcLen = src.length; const dstLen = dst.length; for (let i = 0; i < srcLen; i++) srcLines.push(structHash(src[i])); for (let i = 0; i < dstLen; i++) dstLines.push(structHash(dst[i])); const pfx = path + '/'; const patch = this.patch; const linePatch = line.diff(srcLines, dstLines); const length = linePatch.length; for (let i = length - 1; i >= 0; i--) { const [type, srcIdx, dstIdx] = linePatch[i]; switch (type) { case 0 /* line.LINE_PATCH_OP_TYPE.EQL */: break; case 2 /* line.LINE_PATCH_OP_TYPE.MIX */: { const srcValue = src[srcIdx]; const dstValue = dst[dstIdx]; this.diff(pfx + srcIdx, srcValue, dstValue); break; } case 1 /* line.LINE_PATCH_OP_TYPE.INS */: patch.push({ op: 'add', path: pfx + (srcIdx + 1), value: dst[dstIdx] }); break; case -1 /* line.LINE_PATCH_OP_TYPE.DEL */: patch.push({ op: 'remove', path: pfx + srcIdx }); break; } } } diffAny(path, src, dst) { switch (typeof src) { case 'string': { if (typeof dst === 'string') this.diffStr(path, src, dst); else this.diffVal(path, src, dst); break; } case 'number': case 'boolean': case 'bigint': { this.diffVal(path, src, dst); break; } case 'object': { if (!src || !dst || typeof dst !== 'object') { this.diffVal(path, src, dst); return; } if (Array.isArray(src)) { if (Array.isArray(dst)) this.diffArr(path, src, dst); else this.diffVal(path, src, dst); return; } this.diffObj(path, src, dst); break; } default: this.diffVal(path, src, dst); break; } } diff(path, src, dst) { this.diffAny(path, src, dst); return this.patch; } }