Coder Social home page Coder Social logo

donedeal0 / superdiff Goto Github PK

View Code? Open in Web Editor NEW
437.0 3.0 3.0 224 KB

Superdiff compares two arrays or objects and returns a full diff of their differences in a readable format.

Home Page: https://www.npmjs.com/package/@donedeal0/superdiff

TypeScript 99.88% JavaScript 0.12%
array-comparison comparison comparison-tool deep-diff diff object-comparison object-diff objectdifference objectdiff

superdiff's Introduction

superdiff-logo

SUPERDIFF

This library compares two arrays or objects and returns a full diff of their differences.

Superdiff CI NPM Downloads GitHub Tag

WHY YOU SHOULD USE THIS LIBRARY

All other existing solutions return a strange diff format that often requires additional parsing. They are also limited to object comparison. ๐Ÿ‘Ž

Superdiff gives you a complete diff for both array and objects in a very readable format. Last but not least, it's battle-tested and super fast. Import. Enjoy. ๐Ÿ‘

DONORS

I am grateful to the generous donors of Superdiff!

AlexisAnzieu omonk sneko

DIFF FORMAT COMPARISON

Let's compare the diff format of Superdiff and Deep-diff, the most popular diff lib on npm:

input:

const objectA = {
          id: 54,
          user: {
            name: "joe",
-           member: true,
-           hobbies: ["golf", "football"],
            age: 66,
         },
  }

const objectB = {
        id: 54,
        user: {
            name: "joe",
+           member: false,
+           hobbies: ["golf", "chess"],
            age: 66,
        },
  }

Deep-Diff output:

[
  {
    kind: "E",
    path: ["user", "member"],
    lhs: true,
    rhs: false,
  },
  {
    kind: "E",
    path: ["user", "hobbies", 1],
    lhs: "football",
    rhs: "chess",
  },
];

SuperDiff output:

{
      type: "object",
+     status: "updated",
      diff: [
        {
          property: "id",
          previousValue: 54,
          currentValue: 54,
          status: "equal",
        },
        {
          property: "user",
          previousValue: {
            name: "joe",
            member: true,
            hobbies: ["golf", "football"],
            age: 66,
          },
          currentValue: {
            name: "joe",
            member: false,
            hobbies: ["golf", "chess"],
            age: 66,
          },
+         status: "updated",
          subPropertiesDiff: [
            {
              property: "name",
              previousValue: "joe",
              currentValue: "joe",
              status: "equal",
            },
+           {
+             property: "member",
+             previousValue: true,
+             currentValue: false,
+             status: "updated",
+           },
+           {
+             property: "hobbies",
+             previousValue: ["golf", "football"],
+             currentValue: ["golf", "chess"],
+             status: "updated",
+           },
            {
              property: "age",
              previousValue: 66,
              currentValue: 66,
              status: "equal",
            },
          ],
        },
      ],
    }

FEATURES

Superdiff exports 4 functions:

getObjectDiff()

import { getObjectDiff } from "@donedeal0/superdiff";

Compares two objects and return a diff for each value and their potential subvalues:

  • property name
  • status: added, deleted, equal, updated
  • previous value, current value
  • supports deeply nested objects with any kind of values

format:

type ObjectDiff = {
  type: "object";
  status: "added" | "deleted" | "equal" | "updated";
  diff: {
    property: string;
    previousValue: any;
    currentValue: any;
    status: "added" | "deleted" | "equal" | "updated";
    // only appears if some subproperties have been added/deleted/updated
    subPropertiesDiff?: {
      property: string;
      previousValue: any;
      currentValue: any;
      status: "added" | "deleted" | "equal" | "updated";
      // subDiff is a recursive diff in case of nested subproperties
      subDiff?: SubProperties[];
    }[];
  }[];
};

Options

You can add a third options parameter to getObjectDiff.

{
  ignoreArrayOrder?: boolean // false by default,
  showOnly?: {
    statuses: ("added" | "deleted" |ย "updated" | "equal")[], // [] by default
    granularity?: "basic" | "deep" // "basic" by default
  }
}
  • ignoreArrayOrder: if set to true, ["hello", "world"] and ["world", "hello"] will be treated as equal, because the two arrays have the same value, just not in the same order.

  • showOnly: returns only the values whose status you are interested in. It takes two parameters:

    • statuses: status you want to see in the output (e.g. ["added", "equal"])
      • granularity:
        • basic returns only the main properties whose status matches your query.
        • deep can return main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly.

getListDiff()

import { getListDiff } from "@donedeal0/superdiff";

Compares two arrays and returns a diff for each value:

  • index change: prevIndex, newIndex, indexDiff
  • status: added, deleted, equal, moved, updated
  • value
  • supports arrays of primitive values and objects
  • supports arrays with duplicate values

format:

type ListDiff = {
  type: "list";
  status: "added" | "deleted" | "equal" | "moved" | "updated";
  diff: {
    value: any;
    prevIndex: number | null;
    newIndex: number | null;
    indexDiff: number | null;
    status: "added" | "deleted" | "equal" | "moved" | "updated";
  }[];
};

Options

You can add a third options parameter to getListDiff.

{
  showOnly?: ("added" | "deleted" |ย "moved" | "updated" | "equal")[], // [] by default
  referenceProperty?: string; // "" by default
  ignoreArrayOrder?: boolean // false by default,
}
  • showOnly gives you the option to return only the values whose status you are interested in (e.g. ["added", "equal"]).
  • referenceProperty will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its id. This option has no effect on other datatypes.
  • ignoreArrayOrder: if set to true, ["hello", "world"] and ["world", "hello"] will be treated as equal, because the two arrays have the same value, just not in the same order.

isEqual()

import { isEqual } from "@donedeal0/superdiff";

Tests whether two values are equal.

Options

You can add a third options parameter to isEqual.

{
  ignoreArrayOrder?: boolean // false by default,
}
  • ignoreArrayOrder: if set to true, ["hello", "world"] and ["world", "hello"] will be treated as equal, because the two arrays have the same value, just not in the same order.

isObject()

import { isObject } from "@donedeal0/superdiff";

Tests whether a value is an object.

EXAMPLES

getListDiff()

input

getListDiff(
- ["mbappe", "mendes", "verratti", "ruiz"],
+ ["mbappe", "messi", "ruiz"]
);

output

{
      type: "list",
+     status: "updated",
      diff: [
        {
          value: "mbappe",
          prevIndex: 0,
          newIndex: 0,
          indexDiff: 0,
          status: "equal",
        },
-       {
-         value: "mendes",
-         prevIndex: 1,
-         newIndex: null,
-         indexDiff: null,
-         status: "deleted",
-       },
-       {
-         value: "verratti",
-         prevIndex: 2,
-         newIndex: null,
-         indexDiff: null,
-         status: "deleted",
-       },
+       {
+         value: "messi",
+         prevIndex: null,
+         newIndex: 1,
+         indexDiff: null,
+         status: "added",
+       },
+       {
+         value: "ruiz",
+         prevIndex: 3,
+         newIndex: 2,
+         indexDiff: -1,
+         status: "moved",
        },
      ],
    }

getObjectDiff()

input

getObjectDiff(
  {
    id: 54,
    user: {
      name: "joe",
-     member: true,
-     hobbies: ["golf", "football"],
      age: 66,
    },
  },
  {
    id: 54,
    user: {
      name: "joe",
+     member: false,
+     hobbies: ["golf", "chess"],
      age: 66,
    },
  }
);

output

{
      type: "object",
+     status: "updated",
      diff: [
        {
          property: "id",
          previousValue: 54,
          currentValue: 54,
          status: "equal",
        },
        {
          property: "user",
          previousValue: {
            name: "joe",
            member: true,
            hobbies: ["golf", "football"],
            age: 66,
          },
          currentValue: {
            name: "joe",
            member: false,
            hobbies: ["golf", "chess"],
            age: 66,
          },
+         status: "updated",
          subPropertiesDiff: [
            {
              property: "name",
              previousValue: "joe",
              currentValue: "joe",
              status: "equal",
            },
+           {
+             property: "member",
+             previousValue: true,
+             currentValue: false,
+             status: "updated",
+           },
+           {
+             property: "hobbies",
+             previousValue: ["golf", "football"],
+             currentValue: ["golf", "chess"],
+             status: "updated",
+           },
            {
              property: "age",
              previousValue: 66,
              currentValue: 66,
              status: "equal",
            },
          ],
        },
      ],
    }

isEqual()

isEqual(
  [
    { name: "joe", age: 99 },
    { name: "nina", age: 23 },
  ],
  [
    { name: "joe", age: 98 },
    { name: "nina", age: 23 },
  ]
);

output

false;

isObject()

input

isObject(["hello", "world"]);

output

false;

More examples are available in the source code tests.


CREDITS

DoneDeal0

SUPPORT

If you or your company uses Superdiff, please show your support by becoming a sponsor! Your name and company logo will be displayed on the README.md. https://github.com/sponsors/DoneDeal0


sponsor

CONTRIBUTING

Pull requests are welcome!

superdiff's People

Contributors

donedeal0 avatar mfactorial avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

superdiff's Issues

Marking a property as "unique id" so modified items in array are considered `updated` and not `deleted+added`

Hi @DoneDeal0 ,

I'm trying to compare arrays of objects but I would like the status updated to be triggered when some objects are modified. But for this to be, the library should define a property "stable" in the "before array" and into the "after array".

The current state of the library:

    const before = [
      { id: 1, myProp: 1 },
      { id: 2, myProp: 2 },
      { id: 3, myProp: 3 },
    ];
    const after = [
      { id: 2, myProp: 222 },
      { id: 3, myProp: 3 },
      { id: 4, myProp: 4 },
    ];

    const diffResult = getListDiff(before, after);

would produce:

    + Object {
    +   "diff": Array [
    +     Object {
    +       "indexDiff": null,
    +       "newIndex": null,
    +       "prevIndex": 0,
    +       "status": "deleted",
    +       "value": Object {
    +         "id": 1,
    +         "myProp": 1,
    +       },
    +     },
    +     Object {
    +       "indexDiff": null,
    +       "newIndex": null,
    +       "prevIndex": 1,
    +       "status": "deleted",
    +       "value": Object {
    +         "id": 2,
    +         "myProp": 2,
    +       },
    +     },
    +     Object {
    +       "indexDiff": null,
    +       "newIndex": 0,
    +       "prevIndex": null,
    +       "status": "added",
    +       "value": Object {
    +         "id": 2,
    +         "myProp": 222,
    +       },
    +     },
    +     Object {
    +       "indexDiff": -1,
    +       "newIndex": 1,
    +       "prevIndex": 2,
    +       "status": "moved",
    +       "value": Object {
    +         "id": 3,
    +         "myProp": 3,
    +       },
    +     },
    +     Object {
    +       "indexDiff": null,
    +       "newIndex": 2,
    +       "prevIndex": null,
    +       "status": "added",
    +       "value": Object {
    +         "id": 4,
    +         "myProp": 4,
    +       },
    +     },
    +   ],
    +   "status": "updated",
    +   "type": "list",
    + }

Whereas I expect the object with id: 2 to be updated, no deleted + added. Did I miss something and it's already possible?

A workaround I could try is to make a post-processor that looks at results, for similar id having both status removed and added, keep just one of the two and setting the status to updated. Which would work, and would avoid modifying your library.

Note aside: for other methods you implemented a ignoreArrayOrder. I think it could make sense to add it to getListDiff() to get equal instead of moved (example for id: 3 here), but same here, the developer can just take into account those 2 statuses.

Thank you,

ignoreArrayOrder: true + referenceProperty set should make superdiff disregard changes of position in arrays

I was hoping to get this case to work:

describe('JSON Diff with Superdiff', () => {
const options = { referenceProperty: 'id', ignoreArrayOrder: false };

it('should ignore position changes in arrays', () => {
const original = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
const updated = [{ id: 2, name: 'Bob' }, { id: 1, name: 'Alice' }];

const diff = getObjectDiff(original, updated, options);
expect(diff.status).toBe('equal');

});

But instead it will list both array items as updated. Is there a way to make superdiff disregard changes of items position in an array completely?

[Feature request] Allow filtering of statuses

When dealing with large objects it would be very handy to be able to pass an array of statuses to filter out those you're not interested in.

type Options = {
    discardArrayOrder?: boolean;
    statuses: DiffStatus[]
};
const diff = getObjectDiff(
   audience,
   newAudience,
   { statuses: ["updated"] }
);

Making array order optional

Hey! Thanks for this excellent library!
In one of my use case, I would need to compare two objects and mark them as equals no matter if the elements in their arrays are not in the same order.
eg:

const objectA = {
          id: 54,
          user: {
            name: "joe",
           member: true,
          hobbies: ["golf", "football"],
            age: 66,
         },
  }

const objectB = {
        id: 54,
        user: {
            name: "joe",
           member: true,
           hobbies: [ "football", "golf"],
            age: 66,
        },
  }
  
  // Be marked as equal

Would it be possible to add this kind of option in the API? Thanks a lot!

make changes backward compatible between minor releases

Hey,
Glad to see that the project is receiving continuous improvement!

I am using this lib in a CI / CD pipeline, so changing discardArrayOrder to ignoreArrayOrder in the last update broke the flow.
Would it be possible to keep parameter names backward compatible between minor releases?
Not a big deal but im wondering if we could beneficiate of code improvement without being worried about breaking change.

Thanks a lot!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.