json0-diff 生成两个 json 的 ot 对象

 源码地址。

需要先安装依赖:

npm install deep-equal diff-match-patch

npm i @types/diff-match-patch

npm install --save-dev @types/deep-equal

实现方式: 

import equal from 'deep-equal';
import DiffMatchPatch from 'diff-match-patch';

const diffMatchPatchInstance = new DiffMatchPatch();

type JsonMLPath = (string | number)[];
type Ops = any[];

interface Json1 {
  replaceOp: (path: JsonMLPath, input: any, output: any) => any;
  removeOp: (path: JsonMLPath, input: any) => any;
  insertOp: (path: JsonMLPath, output: any) => any;
  editOp: (path: JsonMLPath, type: any, unicodeOp: any) => any;
  type: {
    compose: (a: any, b: any) => any;
  };
}

function replaceOp(path: JsonMLPath, isObject: boolean, input: any, output: any, json1?: Json1): Ops {
  let op: any;
  if (json1) {
    op = json1.replaceOp(path, input, output);
  } else {
    op = { p: path };
    op[isObject ? 'od' : 'ld'] = input;
    op[isObject ? 'oi' : 'li'] = output;
  }
  return [op];
}

function patchesToOps(
  path: JsonMLPath,
  oldValue: string,
  newValue: string,
  diffMatchPatch: typeof DiffMatchPatch,
  json1?: Json1,
  textUnicode?: any
): Ops {
  const ops: Ops = [];
  const patches = diffMatchPatchInstance.patch_make(oldValue, newValue);

  patches.forEach((patch: any) => {
    let offset = patch.start1;
    // @ts-ignore
    patch.diffs.forEach(([type, value]) => {
      // @ts-ignore
      switch (type) {
        case diffMatchPatch.DIFF_DELETE:
          if (textUnicode) {
            const unicodeOp = textUnicode.remove(offset, value);
            ops.push(json1!.editOp(path, textUnicode.type, unicodeOp));
          } else {
            ops.push({ sd: value, p: [...path, offset] });
          }
          break;
        // @ts-ignore
        case diffMatchPatch.DIFF_INSERT:
          if (textUnicode) {
            const unicodeOp = textUnicode.insert(offset, value);
            ops.push(json1!.editOp(path, textUnicode.type, unicodeOp));
          } else {
            ops.push({ si: value, p: [...path, offset] });
          }
        case diffMatchPatch.DIFF_EQUAL:
          offset += value.length;
          break;
        default:
          throw new Error(`Unsupported operation type: ${type}`);
      }
    });
  });

  return ops;
}

function optimize(ops: Ops, options?: { json1?: Json1 }): Ops {
  if (options && options.json1) {
    const compose = options.json1.type.compose;
    return ops.reduce(compose, null);
  }

  for (let i = 0, l = ops.length - 1; i < l; ++i) {
    const a = ops[i], b = ops[i + 1];

    if (!equal(a.p.slice(0, -1), b.p.slice(0, -1))) {
      continue;
    }

    if (a.p[a.p.length - 1] + 1 !== b.p[b.p.length - 1]) {
      continue;
    }

    if (!a.li || !b.ld) {
      continue;
    }

    if (!equal(a.li, b.ld)) {
      continue;
    }

    delete a.li;
    delete b.ld;
  }

  return ops.filter(op => Object.keys(op).length > 1);
}

function diff(
  input: any,
  output: any,
  path: JsonMLPath = [],
  options?: { diffMatchPatch?: typeof DiffMatchPatch, json1?: Json1, textUnicode?: any }
): Ops {
  const diffMatchPatch = options && options.diffMatchPatch;
  const json1 = options && options.json1;
  const textUnicode = options && options.textUnicode;

  const isObject = typeof path[path.length - 1] === 'string' || path.length === 0;

  if (equal(input, output) && Array.isArray(input) === Array.isArray(output)) {
    return [];
  }

  if (typeof output === 'undefined') {
    let op: any;
    if (json1) {
      op = json1.removeOp(path, input);
    } else {
      op = { p: path };
      op[isObject ? 'od' : 'ld'] = input;
    }
    return [op];
  }

  if (typeof input === 'undefined') {
    let op: any;
    if (json1) {
      op = json1.insertOp(path, output);
    } else {
      op = { p: path };
      op[isObject ? 'oi' : 'li'] = output;
    }
    return [op];
  }

  if (input === null || output === null) {
    const ops: Ops = [];
    if (json1) {
      ops.push(json1.removeOp(path, input));
      ops.push(json1.insertOp(path, output));
    } else {
      const op_delete: any = { p: path };
      op_delete[isObject ? 'od' : 'ld'] = input;
      const op_insert: any = { p: path };
      op_insert[isObject ? 'oi' : 'li'] = output;
      ops.push(op_delete, op_insert);
    }
    return ops;
  }

  if (diffMatchPatch && typeof input === 'string' && typeof output === 'string') {
    return patchesToOps(path, input, output, diffMatchPatch, json1, textUnicode);
  }

  const primitiveTypes = ['string', 'number', 'boolean'];
  if (primitiveTypes.includes(typeof output) || primitiveTypes.includes(typeof input)) {
    return replaceOp(path, isObject, input, output, json1);
  }

  if (Array.isArray(output) && Array.isArray(input)) {
    const ops: Ops = [];
    const inputLen = input.length, outputLen = output.length;
    const minLen = Math.min(inputLen, outputLen);

    for (let i = 0; i < minLen; ++i) {
      const newOps = diff(input[i], output[i], [...path, i], options);
      ops.push(...newOps);
    }

    if (outputLen > inputLen) {
      for (let i = minLen; i < outputLen; i++) {
        const newOps = diff(undefined, output[i], [...path, i], options);
        ops.push(...newOps);
      }
    } else if (outputLen < inputLen) {
      for (let i = minLen; i < inputLen; i++) {
        const newOps = diff(input[i], undefined, [...path, minLen], options);
        ops.push(...newOps);
      }
    }
    return ops;
  } else if (Array.isArray(output) || Array.isArray(input)) {
    return replaceOp(path, isObject, input, output, json1);
  }

  let ops: Ops = [];
  const keys = new Set([...Object.keys(input), ...Object.keys(output)]);
  keys.forEach(key => {
    const newOps = diff(input[key], output[key], [...path, key], options);
    ops = ops.concat(newOps);
  });
  return ops;
}

function optimizedDiff(
  input: any,
  output: any,
  diffMatchPatch?: typeof DiffMatchPatch,
  json1?: Json1,
  textUnicode?: any
): Ops {
  const options = { diffMatchPatch, json1, textUnicode };
  return optimize(diff(input, output, [], options), options);
}

export { optimizedDiff };

调用方式:

    const a = [
      {
        "recordId": "rec00001"
      },
      {
        "recordId": "rec00002"
      },
      {
        "recordId": "rec00003"
      },
      {
        "recordId": "rec00008"
      },
      {
        "recordId": "rec00004"
      }
    ];
    const b = [
      {
        "recordId": "rec00001"
      },
      {
        "recordId": "rec00005"
      },
      {
        "recordId": "rec00002"
      },
      {
        "recordId": "rec00003"
      },
      {
        "recordId": "rec00007"
      },
      {
        "recordId": "rec00004"
      }
    ];
    var diff = optimizedDiff(
      a,
      b
    );

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值