utils.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { compare, Operation } from 'fast-json-patch';
  2. // @ts-ignore
  3. import jsonMap from 'json-source-map';
  4. import { flow, get, isArray, isEmpty, last, sortBy, tail, toNumber, isNaN } from 'lodash';
  5. export type Diff = {
  6. op: 'add' | 'replace' | 'remove' | 'copy' | 'test' | '_get' | 'move';
  7. value: any;
  8. originalValue: any;
  9. path: string[];
  10. startLineNumber: number;
  11. };
  12. export type Diffs = {
  13. [key: string]: Diff[];
  14. };
  15. export const jsonDiff = (lhs: any, rhs: any): Diffs => {
  16. const diffs = compare(lhs, rhs);
  17. const lhsMap = jsonMap.stringify(lhs, null, 2);
  18. const rhsMap = jsonMap.stringify(rhs, null, 2);
  19. const getDiffInformation = (diffs: Operation[]): Diff[] => {
  20. return diffs.map((diff) => {
  21. let originalValue = undefined;
  22. let value = undefined;
  23. let startLineNumber = 0;
  24. const path = tail(diff.path.split('/'));
  25. if (diff.op === 'replace') {
  26. originalValue = get(lhs, path);
  27. value = diff.value;
  28. startLineNumber = rhsMap.pointers[diff.path].value.line;
  29. }
  30. if (diff.op === 'add') {
  31. value = diff.value;
  32. startLineNumber = rhsMap.pointers[diff.path].value.line;
  33. }
  34. if (diff.op === 'remove') {
  35. originalValue = get(lhs, path);
  36. startLineNumber = lhsMap.pointers[diff.path].value.line;
  37. }
  38. return {
  39. op: diff.op,
  40. value,
  41. path,
  42. originalValue,
  43. startLineNumber,
  44. };
  45. });
  46. };
  47. const sortByLineNumber = (diffs: Diff[]) => sortBy(diffs, 'startLineNumber');
  48. const groupByPath = (diffs: Diff[]) =>
  49. diffs.reduce<Record<string, any>>((acc, value) => {
  50. const groupKey: string = value.path[0];
  51. if (!acc[groupKey]) {
  52. acc[groupKey] = [];
  53. }
  54. acc[groupKey].push(value);
  55. return acc;
  56. }, {});
  57. return flow([getDiffInformation, sortByLineNumber, groupByPath])(diffs);
  58. };
  59. export const getDiffText = (diff: Diff, showProp = true) => {
  60. const prop = last(diff.path)!;
  61. const propIsNumeric = isNumeric(prop);
  62. const val = diff.op === 'remove' ? diff.originalValue : diff.value;
  63. let text = getDiffOperationText(diff.op);
  64. if (showProp) {
  65. if (propIsNumeric) {
  66. text += ` item ${prop}`;
  67. } else {
  68. if (isArray(val) && !isEmpty(val)) {
  69. text += ` ${val.length} ${prop}`;
  70. } else {
  71. text += ` ${prop}`;
  72. }
  73. }
  74. }
  75. return text;
  76. };
  77. const isNumeric = (value: string) => !isNaN(toNumber(value));
  78. export const getDiffOperationText = (operation: string): string => {
  79. if (operation === 'add') {
  80. return 'added';
  81. }
  82. if (operation === 'remove') {
  83. return 'deleted';
  84. }
  85. return 'changed';
  86. };