From bf436c94ab269f3a85cb11a0d562df410d75c0d3 Mon Sep 17 00:00:00 2001 From: GrayHat Date: Wed, 11 Oct 2023 23:07:26 +0530 Subject: [PATCH] improved comparison algorithm, improved highlight logic --- src/pages/Compare.tsx | 113 ++++++++++++++++++++++++++++++++++-------- src/utils/butils.ts | 2 +- src/utils/index.ts | 97 ++++++++++++++++++++++++++++++++++-- 3 files changed, 186 insertions(+), 26 deletions(-) diff --git a/src/pages/Compare.tsx b/src/pages/Compare.tsx index 32b32db..8eedd3b 100644 --- a/src/pages/Compare.tsx +++ b/src/pages/Compare.tsx @@ -8,7 +8,7 @@ import Loading from "./Loading"; import { BiSolidUpArrow, BiSolidDownArrow } from "react-icons/bi"; import { sortObj, cleanJSON } from "jsonabc"; import styles from "./compare.module.css"; -import { JSONDiff, Difference, difference } from "../utils"; +import { JSONDiff, Difference, differenceV2 } from "../utils"; import { parseJSONPath } from "../utils/butils"; import * as utils from "../utils"; import { useWorker, WORKER_STATUS } from "../worker"; @@ -49,33 +49,66 @@ function uniqueDifferenceFunction(differenceObject: Difference) { // } // })]; left.different.filter((x, index) => !left.different.find((y, _index) => y.startsWith(x) && index != _index)).forEach((path) => { - if (right.different.includes(path)) { - uniques.push({ pathLeft: path, pathRight: path }); - } + uniques.push({ pathLeft: path, pathRight: path }); }); // tasks = [...tasks, ...left.extra.map(async (path) => { // if (!right.extra.includes(path)) { // uniques.push({ pathLeft: path }); // } // })]; - left.extra.forEach((path) => { - if (!right.extra.includes(path)) { - uniques.push({ pathLeft: path }); - } + left.extra.filter((x, index) => !left.extra.find((y, _index) => y.startsWith(x) && index != _index)).forEach((path) => { + uniques.push({ pathLeft: path }); }); // tasks = [...tasks, ...right.extra.map(async (path) => { // if (!left.extra.includes(path)) { // uniques.push({ pathRight: path }); // } // })]; - right.extra.forEach((path) => { - if (!left.extra.includes(path)) { - uniques.push({ pathRight: path }); - } + right.extra.filter((x, index) => !right.extra.find((y, _index) => y.startsWith(x) && index != _index)).forEach((path) => { + uniques.push({ pathRight: path }); }); // await Promise.all(tasks); // if (uniqueDifferenceInterval) clearInterval(uniqueDifferenceInterval); - uniqueDifferenceInterval = undefined; + // uniqueDifferenceInterval = undefined; + // setUniqueDiff(uniques); + // setCurrentDifferenceIndex(uniques.length === 0 ? 0 : 1); + return { uniques, index: uniques.length === 0 ? 0 : 1 }; +} + +function uniqueDifferenceFunctionV2(differenceObject: Difference) { + let left = differenceObject.left; + let right = differenceObject.right; + let uniques: { pathLeft?: string; pathRight?: string; }[] = []; + // let tasks: Promise[] = []; + // console.log("Finding unique difference", left, right); + // let a = left.different.filter(async (x, index) => !left.different.find((y, _index) => y.startsWith(x) && index != _index)); + // tasks = [...tasks, ...left.different.filter((x, index) => !left.different.find((y, _index) => y.startsWith(x) && index != _index)).map(async (path) => { + // if (right.different.includes(path)) { + // uniques.push({ pathLeft: path, pathRight: path }); + // } + // })]; + left.different.forEach((path) => { + uniques.push({ pathLeft: path, pathRight: path }); + }); + // tasks = [...tasks, ...left.extra.map(async (path) => { + // if (!right.extra.includes(path)) { + // uniques.push({ pathLeft: path }); + // } + // })]; + left.missing.forEach((path) => { + uniques.push({ pathLeft: path }); + }); + // tasks = [...tasks, ...right.extra.map(async (path) => { + // if (!left.extra.includes(path)) { + // uniques.push({ pathRight: path }); + // } + // })]; + right.extra.filter((x, index) => !right.extra.find((y, _index) => y.startsWith(x) && index != _index)).forEach((path) => { + uniques.push({ pathRight: path }); + }); + // await Promise.all(tasks); + // if (uniqueDifferenceInterval) clearInterval(uniqueDifferenceInterval); + // uniqueDifferenceInterval = undefined; // setUniqueDiff(uniques); // setCurrentDifferenceIndex(uniques.length === 0 ? 0 : 1); return { uniques, index: uniques.length === 0 ? 0 : 1 }; @@ -83,6 +116,42 @@ function uniqueDifferenceFunction(differenceObject: Difference) { const STYLE_ID = "jsoneditor-styles-custom"; +function generateStylesV2(editorid: string, classNames: {[classname: string]: string[]}) { + let style: string[] = []; + Object.keys(classNames).forEach(className => { + classNames[className].forEach(datapath => { + let selector = `#${editorid} div[data-path="${datapath}"] > div`; + let rules: string[] = [ + "color:#292D1C!important", + "--jse-key-color:#292D1C!important", + "background-color:var(--background-color-custom)!important", + "--jse-selection-background-inactive-color:var(--background-color-custom)!important", + "--jse-value-color-string:#292D1C" + ]; + switch (className) { + case styles.different: { + rules.push('--jse-contents-background-color: #f6d283;'); + break; + }; + case styles.missing: { + rules.push('--jse-contents-background-color: #f69283;'); + break; + }; + case styles.extra: { + rules.push('--jse-contents-background-color: #c5da8b;'); + break; + }; + default: { + console.error("Should never happen", editorid, classNames, className); + break; + } + } + style.push(`${selector}{${rules.join(";")}}`); + }); + }); + return style.join(''); +} + function generateStyles(editorid: string, classNames: { [classname: string]: string[] }) { let style2 = `color:#292D1C!important;--jse-key-color:#292D1C!important;background-color:var(--background-color-custom)!important`; let style3 = `color:#292D1C!important;--jse-selection-background-inactive-color:var(--background-color-custom)!important`; @@ -107,7 +176,7 @@ function generateStyles(editorid: string, classNames: { [classname: string]: str let val = `--background-color-custom:${className == styles.different ? "#F6D283" : className == styles.extra ? "#C5DA8B" : "#ED8373"}` return selectors.size > 0 ? `${Array.from(selectors.values()).join(',')}{${val}}` : ""; }); - return style1s.join('') + style2 + style3 + return style1s.join('') + style2 + style3; // let style = ` // #${editorid} div[data-path="${datapath}"] { // --background-color-custom: ${className == styles.different ? "#F6D283" : className == styles.extra ? "#C5DA8B" : "#ED8373"}; @@ -133,8 +202,8 @@ export default function Compare() { const [rightMode, setRightMode] = useState(Mode.tree); const [currentDifferenceIndex, setCurrentDifferenceIndex] = useState(0); const [loadingState, setLoadingState] = useState(false); - const [uniqueDifferenceWorker, { status: differenceWorkerStatus, kill: killDifferenceWorker }] = useWorker(uniqueDifferenceFunction); - const [differenceFinderWorker, { status: differenceFinderWorkerStatus, kill: killDifferenceFinderWorker }] = useWorker(difference, { + const [uniqueDifferenceWorker, { status: differenceWorkerStatus, kill: killDifferenceWorker }] = useWorker(uniqueDifferenceFunctionV2); + const [differenceFinderWorker, { status: differenceFinderWorkerStatus, kill: killDifferenceFinderWorker }] = useWorker(differenceV2, { localDependencies() { return { ...utils, cache: {} } }, @@ -320,7 +389,7 @@ export default function Compare() { } useEffect(() => { - // console.log("Running useEffect"); + console.log("Running useEffect", differenceObject); if (highlightJobInterval) clearInterval(highlightJobInterval); highlightJobInterval = setInterval(regularHighlightJob, 100); return () => { @@ -337,11 +406,13 @@ export default function Compare() { killDifferenceWorker(); console.log('Starting unique difference worker'); uniqueDifferenceWorker(differenceObject).then(result => { + console.log('uniques', result); setUniqueDiff(result.uniques); setCurrentDifferenceIndex(result.index); }).catch(console.error).finally(() => { - if (uniqueDifferenceInterval) clearInterval(uniqueDifferenceInterval); + console.log("FINISHED"); }); + if (uniqueDifferenceInterval) clearInterval(uniqueDifferenceInterval); } useEffect(() => { @@ -352,7 +423,7 @@ export default function Compare() { if (uniqueDifferenceInterval) { clearInterval(uniqueDifferenceInterval); } - uniqueDifferenceInterval = setInterval(uniqueDifferenceFunctionRunner, 20); + uniqueDifferenceInterval = setInterval(uniqueDifferenceFunctionRunner, 100); return () => { if (uniqueDifferenceInterval) clearInterval(uniqueDifferenceInterval); }; @@ -386,12 +457,12 @@ export default function Compare() { styleElement.id = STYLE_ID; let [leftStyle, rightStyle] = await Promise.all(tasks); let returnables = [leftStyle.returnable, rightStyle.returnable]; - let leftcss = generateStyles(leftId, { + let leftcss = generateStylesV2(leftId, { [styles.different]: leftStyle.result.find((x) => x.style === styles.different)?.paths || [], [styles.extra]: leftStyle.result.find((x) => x.style === styles.extra)?.paths || [], [styles.missing]: leftStyle.result.find((x) => x.style === styles.missing)?.paths || [], }); - let rightcss = generateStyles(rightId, { + let rightcss = generateStylesV2(rightId, { [styles.different]: rightStyle.result.find((x) => x.style === styles.different)?.paths || [], [styles.extra]: rightStyle.result.find((x) => x.style === styles.extra)?.paths || [], [styles.missing]: rightStyle.result.find((x) => x.style === styles.missing)?.paths || [], diff --git a/src/utils/butils.ts b/src/utils/butils.ts index ddfc7bc..86e06f1 100644 --- a/src/utils/butils.ts +++ b/src/utils/butils.ts @@ -17,7 +17,7 @@ export function parseJSONPath(path: string): JSONPath { index += 1; start_index = index; } else { - console.log('skipping', char); + // console.log('skipping', path, char); index += 1; } } break; diff --git a/src/utils/index.ts b/src/utils/index.ts index f25c3ca..01692bd 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -36,7 +36,7 @@ export function cyrb53(str: string, seed: number = 0) { }; export function hashCode(s: string) { - for(var i = 0, h = 0; i < s.length; i++) + for (var i = 0, h = 0; i < s.length; i++) h = Math.imul(31, h) + s.charCodeAt(i) | 0; return h; } @@ -97,9 +97,9 @@ export function normalizeArray(objects: any[], baseKey = "$") { // normalized[`${baseKey}[${index}]`] = object; // } // })); - objects.forEach(async (object: any, index: number) => { + objects.forEach((object: any, index: number) => { if (isPlainObject(object)) { - for (let [key, value] of Object.entries(await normalizeObject(object, `${baseKey}[${index}]`))) { + for (let [key, value] of Object.entries(normalizeObject(object, `${baseKey}[${index}]`))) { normalized[key] = value; } // normalized = { @@ -107,7 +107,7 @@ export function normalizeArray(objects: any[], baseKey = "$") { // ...await normalizeObject(object, `${baseKey}[${index}]`), // }; } else if (isArray(object)) { - for (let [key, value] of Object.entries(await normalizeArray(object, `${baseKey}[${index}]`))) { + for (let [key, value] of Object.entries(normalizeArray(object, `${baseKey}[${index}]`))) { normalized[key] = value; } // normalized = { @@ -258,3 +258,92 @@ export function difference(jsona: any, jsonb: any) { return null; } } + +export function differenceV2(jsona: any, jsonb: any, baseKey: string = "$") { + if (!baseKey.endsWith(".")) { + baseKey += "."; + } + let diff: Difference = { + left: { + different: [], + extra: [], + missing: [], + }, + right: { + different: [], + extra: [], + missing: [] + }, + }; + + if (isPlainObject(jsona) && isPlainObject(jsonb)) { + Object.keys(jsona).forEach(keya => { + let newkey = keya.replace("'", "\\'").replace('"', '\\"'); + if (newkey.includes(".")) { + newkey = `"${newkey}"`; + } + let path = `${baseKey}${newkey}`; + if (typeof jsonb[keya] !== "undefined") { + let vala = jsona[keya]; + let valb = jsonb[keya]; + if (typeof vala !== typeof valb) { + diff.left.different.push(path); + diff.right.different.push(path); + } else if ((isPlainObject(vala) && isPlainObject(valb)) || (isArray(vala) && isArray(valb))) { + let _diff = differenceV2(vala, valb, path); + diff.left.different = diff.left.different.concat(_diff.left.different); + diff.right.different = diff.right.different.concat(_diff.right.different); + diff.left.extra = diff.left.extra.concat(_diff.left.extra); + diff.right.extra = diff.right.extra.concat(_diff.right.extra); + diff.left.missing = diff.left.missing.concat(_diff.left.missing); + diff.right.missing = diff.right.missing.concat(_diff.right.missing); + } else if (vala !== valb) { + diff.left.different.push(path); + diff.right.different.push(path); + } + } + else { + diff.left.missing.push(path); + // diff.right.extra.push(path); + } + }); + Object.keys(jsonb).forEach(keyb => { + let newkey = keyb.replace("'", "\\'").replace('"', '\\"'); + if (newkey.includes(".")) { + newkey = `"${newkey}"`; + } + let path = `${baseKey}${newkey}`; + if (typeof jsona[keyb] === "undefined") { + diff.right.extra.push(path); + } + }); + } else if (isArray(jsona) && isArray(jsonb)) { + for (let i = 0; i < jsona.length; i++) { + let path = `${baseKey}.[${i}]`; + let vala = jsona[i]; + let valb = jsonb[i]; + if (typeof vala !== typeof valb) { + diff.left.different.push(path); + diff.right.different.push(path); + } else if ((isPlainObject(vala) && isPlainObject(valb)) || (isArray(vala) && isArray(valb))) { + let _diff = differenceV2(vala, valb, path); + diff.left.different = diff.left.different.concat(_diff.left.different); + diff.right.different = diff.right.different.concat(_diff.right.different); + diff.left.extra = diff.left.extra.concat(_diff.left.extra); + diff.right.extra = diff.right.extra.concat(_diff.right.extra); + diff.left.missing = diff.left.missing.concat(_diff.left.missing); + diff.right.missing = diff.right.missing.concat(_diff.right.missing); + } else if (vala !== valb) { + diff.left.different.push(path); + diff.right.different.push(path); + } + } + for (let i = jsona.length; i < jsonb.length; i++) { + let path = `${baseKey}.[${i}]`; + diff.right.extra.push(path); + } + } else { + throw Error("Invalid input JSON"); + } + return diff; +} \ No newline at end of file