Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improved comparison algorithm, improved highlight logic #6

Merged
merged 2 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 93 additions & 21 deletions src/pages/Compare.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -49,40 +49,110 @@ 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<void>[] = [];
// 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 };
}

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",
"--jse-delimiter-color:#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`;
Expand All @@ -107,7 +177,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"};
Expand All @@ -133,8 +203,8 @@ export default function Compare() {
const [rightMode, setRightMode] = useState<Mode>(Mode.tree);
const [currentDifferenceIndex, setCurrentDifferenceIndex] = useState<number>(0);
const [loadingState, setLoadingState] = useState<boolean>(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: {} }
},
Expand Down Expand Up @@ -320,7 +390,7 @@ export default function Compare() {
}

useEffect(() => {
// console.log("Running useEffect");
console.log("Running useEffect", differenceObject);
if (highlightJobInterval) clearInterval(highlightJobInterval);
highlightJobInterval = setInterval(regularHighlightJob, 100);
return () => {
Expand All @@ -337,11 +407,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(() => {
Expand All @@ -352,7 +424,7 @@ export default function Compare() {
if (uniqueDifferenceInterval) {
clearInterval(uniqueDifferenceInterval);
}
uniqueDifferenceInterval = setInterval(uniqueDifferenceFunctionRunner, 20);
uniqueDifferenceInterval = setInterval(uniqueDifferenceFunctionRunner, 100);
return () => {
if (uniqueDifferenceInterval) clearInterval(uniqueDifferenceInterval);
};
Expand Down Expand Up @@ -386,12 +458,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 || [],
Expand Down
2 changes: 1 addition & 1 deletion src/utils/butils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
97 changes: 93 additions & 4 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -97,17 +97,17 @@ 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 = {
// ...normalized,
// ...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 = {
Expand Down Expand Up @@ -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;
}