Skip to content

Instantly share code, notes, and snippets.

@anastaciocintra
Last active June 26, 2024 20:25
Show Gist options
  • Save anastaciocintra/6a9bf013d8bd940f857d5c9ad08990e1 to your computer and use it in GitHub Desktop.
Save anastaciocintra/6a9bf013d8bd940f857d5c9ad08990e1 to your computer and use it in GitHub Desktop.
Compare two objects JS

Compare two data objects / arrays in javascript and/or typescript regardless of the order of the data;

When to use:

  • When you need compare two "json" objects to know if it have same data.
  • When the order of the array doesn't matters ie. [1,2] should be equal [2,1], but it is configurable.
  • lightweight - just one small .js file

when not to use:

  • when you need to compare more complex objects, with functions, for example.
  • when you need a list with all differences between the two objects

expected results:

param 1 param 2 isEqual
{ a: 1, b: 2 } { a: 1, b: 2 } true
{ a: 1, b: 2 } { b: 2, a: 1 } true
{ a: 1, b: 3 } { b: 2, a: 1 } false
param 1 param 2 isEqual
[1, 2] [1, 2] true
[1, 2] [2, 1] true
[1, 2] [3, 4] false

but with checkDataOrder , the order of arrays matters:

checkDataOrder param 1 param 2 isEqual
false (default) [1, 2] [2, 1] true
true [1, 2] [2, 1] false
false (default) { a: [3, 1, 2], b: 2 } { b: 2, a: [1, 2, 3] } true
true { a: [3, 1, 2], b: 2 } { b: 2, a: [1, 2, 3] } false
  /////////////////////////////////
  // whith checkDataOrder = false (default value is false)
  console.log(isEqual(null, 12)); //false
  console.log(isEqual(undefined, NaN)); //false
  console.log(isEqual(undefined, undefined)); //true
  console.log(isEqual(undefined, null)); //false
  console.log(isEqual(null, null)); //true
  console.log(isEqual(BigInt("1"), 1)); //false
  console.log(isEqual(Number(1), 1)); // true
  console.log(isEqual({ a: 1, b: 2 }, { b: 2, a: 1 })); // true
  console.log(isEqual({ a: [3, 1, 2], b: 2 }, { b: 2, a: [1, 2, 3] })); // true
  console.log(isEqual([1, 2], [1, 2])); // true
  /////////////////////////////////
  // whith checkDataOrder = true
  console.log(isEqual({ a: 1, b: 2 }, { b: 2, a: 1 }, true)); //  expected result true
  console.log(isEqual([1, 2], [2, 1], true)); // expected result false
  console.log(isEqual({ a: [3, 1, 2], b: 2 }, { b: 2, a: [1, 2, 3] }, true)); // expected result false
/**
* @license
* This code is licensed under the terms of the MIT license
*
* Copyright (c) 2022 Marco Antonio Anastacio Cintra <anastaciocintra@gmail.com>
* and contributors:
* "@bayerlse" - Sebastian Bayerl
* "@jrson83" -
* "@bsor-dev"
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
/**
* Compare two data objects / arrays in javascript.
*
* @author Marco Antonio Anastacio Cintra
* @link https://gist.github.com/anastaciocintra/6a9bf013d8bd940f857d5c9ad08990e1
* @param {any} obj1 the first object.
* @param {any} obj2 the second object.
* @param {boolean} checkDataOrder determine if order data is relevant on comparison
*
* @returns {boolean} return true if obj1 and obj2 have the same data
*
* @example
*
* expected results:
* { a: 1, b: 2 } === { a: 1, b: 2 }
* { a: 1, b: 2 } === { b: 2, a: 1 }
* { a: 1, b: 3 } !== { b: 2, a: 1 }
* [1, 2] === [1, 2]
* [2, 1] === [1, 2]
* [1, 3] !== [1, 2]
*
* but with checkDataOrder = true, the results are different for arrays:
* [1, 2] === [1, 2] // same result as without checkDataOrder
* [2, 1] !== [1, 2] // different order
* [1, 3] !== [1, 2] // same result as without checkDataOrder
*
*/
export const isEqual = (obj1, obj2, checkDataOrder = false) => {
const checkDataOrderParanoic = false;
if (obj1 === null || obj2 === null) {
return obj1 === obj2;
}
let _obj1 = obj1;
let _obj2 = obj2;
if (!checkDataOrder) {
if (obj1 instanceof Array) {
_obj1 = [...obj1].sort();
}
if (obj2 instanceof Array) {
_obj2 = [...obj2].sort();
}
}
if (typeof _obj1 !== "object" || typeof _obj2 !== "object") {
return _obj1 === _obj2;
}
const obj1Props = Object.getOwnPropertyNames(_obj1);
const obj2Props = Object.getOwnPropertyNames(_obj2);
if (obj1Props.length !== obj2Props.length) {
return false;
}
if (checkDataOrderParanoic && checkDataOrder) {
// whill result in {a:1, b:2} !== {b:2, a:1}
// its not normal, but if you want this behavior, set checkDataOrderParanoic = true
const propOrder = obj1Props.toString() === obj2Props.toString();
if (!propOrder) {
return false;
}
}
for (const prop of obj1Props) {
const val1 = _obj1[prop];
const val2 = _obj2[prop];
if (typeof val1 === "object" && typeof val2 === "object") {
if (isEqual(val1, val2, checkDataOrder)) {
continue;
} else {
return false;
}
}
if (val1 !== val2) {
return false;
}
}
return true;
};
/**
* @license
* This code is licensed under the terms of the MIT license
*
* Copyright (c) 2022 Marco Antonio Anastacio Cintra <anastaciocintra@gmail.com>
* and contributors:
* "@bayerlse" - Sebastian Bayerl
* "@jrson83" -
* "@bsor-dev"
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
/**
* Compare two data objects / arrays in typescript.
*
* @author Marco Antonio Anastacio Cintra
* @link https://gist.github.com/anastaciocintra/6a9bf013d8bd940f857d5c9ad08990e1
* @param {any} obj1 the first object.
* @param {any} obj2 the second object.
* @param {boolean} checkDataOrder determine if order data is relevant on comparison
*
* @returns {boolean} return true if obj1 and obj2 have the same data
*
* @example
*
* expected results:
* { a: 1, b: 2 } === { a: 1, b: 2 }
* { a: 1, b: 2 } === { b: 2, a: 1 }
* { a: 1, b: 3 } !== { b: 2, a: 1 }
* [1, 2] === [1, 2]
* [2, 1] === [1, 2]
* [1, 3] !== [1, 2]
*
* but with checkDataOrder = true, the results are different for arrays:
* [1, 2] === [1, 2] // same result as without checkDataOrder
* [2, 1] !== [1, 2] // different order
* [1, 3] !== [1, 2] // same result as without checkDataOrder
*
*/
export const isEqual = (obj1: any, obj2: any, checkDataOrder: boolean = false): boolean => {
const checkDataOrderParanoic = false;
if (obj1 === null || obj2 === null) {
return obj1 === obj2;
}
let _obj1 = obj1;
let _obj2 = obj2;
if (!checkDataOrder) {
if (obj1 instanceof Array) {
_obj1 = [...obj1].sort();
}
if (obj2 instanceof Array) {
_obj2 = [...obj2].sort();
}
}
if (typeof _obj1 !== "object" || typeof _obj2 !== "object") {
return _obj1 === _obj2;
}
const obj1Props = Object.getOwnPropertyNames(_obj1);
const obj2Props = Object.getOwnPropertyNames(_obj2);
if (obj1Props.length !== obj2Props.length) {
return false;
}
if (checkDataOrderParanoic && checkDataOrder) {
// whill result in {a:1, b:2} !== {b:2, a:1}
// its not normal, but if you want this behavior, set checkDataOrderParanoic = true
const propOrder = obj1Props.toString() === obj2Props.toString();
if (!propOrder) {
return false;
}
}
for (const prop of obj1Props) {
const val1 = _obj1[prop];
const val2 = _obj2[prop];
if (typeof val1 === "object" && typeof val2 === "object") {
if (isEqual(val1, val2, checkDataOrder)) {
continue;
} else {
return false;
}
}
if (val1 !== val2) {
return false;
}
}
return true;
};
@anastaciocintra
Copy link
Author

hi @jrson83,
I never thought about it, but yeap you can use it for free, I did a little research about licensing gists, and put a @license at top of document.
I don't know if it's the best way, if you know a better way to do this let me know.
see you.

@jrson83
Copy link

jrson83 commented Dec 7, 2022

hi @jrson83, I never thought about it, but yeap you can use it for free, I did a little research about licensing gists, and put a @license at top of document. I don't know if it's the best way, if you know a better way to do this let me know. see you.

Cool, thanks. Guess this is totally fine!

@bsor-dev
Copy link

bsor-dev commented Dec 8, 2022

Do we have typescript version available

@jrson83
Copy link

jrson83 commented Dec 8, 2022

Do we have typescript version available

I tried to type the objects as unknown, but when bundling with microbundle I couldn't get around the error:

export default function isEqual(obj1: unknown, obj2: unknown, checkDataOrder = false): boolean {
  // semantic error TS2571: Object is of type 'unknown

  // ...

 for (const prop of obj1Props) {
    const val1 = obj1![prop as keyof typeof obj1]
    const val2 = obj2![prop as keyof typeof obj2]

    // ...
  }
}

I replaced unknown with any to be able to bundle. If someone has a better solution would be great,

@anastaciocintra
Copy link
Author

yeap, just change this line:
export const isEqual = (obj1: any, obj2: any, checkDataOrder: boolean = false) => {
I'm adding another file for using with typescript ...

@anastaciocintra
Copy link
Author

yeap, just change this line: export const isEqual = (obj1: any, obj2: any, checkDataOrder: boolean = false) => { I'm adding another file for using with typescript ...

my mistake, missing boolean return.
export const isEqual = (obj1: any, obj2: any, checkDataOrder: boolean = false): boolean => {

or, simply copy new isEqual.ts file
any problem, let me know.

about my test environment:
react 18 with typescript;
my version of typescript is 4.8.4
my tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

@anastaciocintra
Copy link
Author

Do we have typescript version available

I tried to type the objects as unknown, but when bundling with microbundle I couldn't get around the error:

export default function isEqual(obj1: unknown, obj2: unknown, checkDataOrder = false): boolean {
  // semantic error TS2571: Object is of type 'unknown

  // ...

 for (const prop of obj1Props) {
    const val1 = obj1![prop as keyof typeof obj1]
    const val2 = obj2![prop as keyof typeof obj2]

    // ...
  }
}

I replaced unknown with any to be able to bundle. If someone has a better solution would be great,

great!
any works fine for me

@bayerlse
Copy link

Hey there, nice function! Just wanted to tell, that maybe sort shouldn't be used on the original parameter, as its destructive.

So better do:

if (obj1 instanceof Array) {
    _obj1 = [...obj1].sort();
}
if (obj2 instanceof Array) {
    _obj2 = [...obj2].sort();
}

@anastaciocintra
Copy link
Author

great job @bayerlse,
corrections was made!
I changed in three points:

if (obj1 instanceof Array) {
    _obj1 = [...obj1].sort();
}
if (obj2 instanceof Array) {
    _obj2 = [...obj2].sort();
}

and (lines 93 and 94...)

 for (const prop of obj1Props) {
   const val1 = _obj1[prop];
   const val2 = _obj2[prop];

and in Contributors (MIT license)

@jrson83
Copy link

jrson83 commented Mar 19, 2023

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment