'How can I compare 2 nested objects, subtracting the differences & returning the equals?

I have the below 2 variables.

const val1 = {
    students: {
      grade: [
        {
          subject: "Math3",
          name: "jack",
          mark: 60,
          attendance: 90,
          abscent: 2,
        },
      ],
      class: [
        {
          className: "math3",
          students: 50,
          absenteeism: 10,
          requirment: [
            {
              subject: "math1",
              mark: 60,
              pass: 51,
            },
            {
                subject: "math2",
                mark: 60,
                pass: 51,
            },
          ],
        },
      ],
    },
  };
  
  const val2 = {
    students: {
        grade: [
          {
            subject: "Math3",
            name: "jack",
            mark: 80
          },
        ],
        class: [
          {
            className: "math3",
            students: 40,
            absenteeism: 10,
            requirment: [
              {
                subject: "math1",
                mark: 75,
                pass: 51,
              },
              {
                  subject: "math2",
                  mark: 90,
                  pass: 51,
              },
            ],
          },
        ],
      },
  };

I am trying to get the below result by returning the the key and value if they are the same, & if the key is a number, return the difference of that number & if the item doesn't exist, skip. Like so.

  students: {
    grade: [
      {
        subject: "Math3",
        name: "jack",
        diff: 20
      },
    ],
    class: [
      {
        className: "math3",
        diff: 10,
        requirment: [
          {
            subject: "math1",
            diff: 15,
          },
          {
              subject: "math2",
              diff: 30,
          },
        ],
      },
    ],
  },
};

Code so far. I get the values but not the way i want it. Also, is there a more cleaner way.

  const changeTable = (table1, table2) => {
    const result = {};
    result["students"] = {};

    let file1= table1.students
    let file2=table2.students

 

for (let x in file1) {
    if (file2[x]) {
      result.students[x] =file1[x]
      .map((y) => file2[x]
      .map((z)=> Object.keys(y)
      .map((key)=> {
        
        if (y[key] === z[key]) {
            return {[key]:y[key]}
        }

        if (y[key] !== z[key]) {
            if (typeof y[key] === "number") {
                const res= Number(z[key])-Number(y[key])
                if (!isNaN(res)) {
                    return {[key]:res}
                }
            }
        }

        }
        
      ).filter(i =>i)))
    }
  }
  return result
  };



changeTable(val1, val2)

Any advice is much appreciated.



Solution 1:[1]

I tried to match your expected output as closely as possible. In general terms, this is not a task suitable for array methods like map because what you really need here is recursive traversal of objects. I added comments to the code to show what it's doing, in case something is not quite right.

const val1 = {
    "students": {
        "grade": [
            {
                "subject": "Math3",
                "name": "jack",
                "mark": 60,
                "attendance": 90,
                "abscent": 2
            }
        ],
        "class": [
            {
                "className": "math3",
                "students": 50,
                "absenteeism": 10,
                "requirment": [
                    {
                        "subject": "math1",
                        "mark": 60,
                        "pass": 51
                    },
                    {
                        "subject": "math2",
                        "mark": 60,
                        "pass": 51
                    }
                ]
            }
        ]
    }
}

const val2 = {
    "students": {
        "grade": [
            {
                "subject": "Math3",
                "name": "jack",
                "mark": 80
            }
        ],
        "class": [
            {
                "className": "math3",
                "students": 40,
                "absenteeism": 10,
                "requirment": [
                    {
                        "subject": "math1",
                        "mark": 75,
                        "pass": 51
                    },
                    {
                        "subject": "math2",
                        "mark": 90,
                        "pass": 51
                    }
                ]
            }
        ]
    }
}

function diffObject(obj1, obj2) {
    let result = {}
    for (let key in obj1) {
        // ignore properties not present in both objects
        if (!(key in obj2)) continue
        // ignore not same type, such as a number and string
        if (typeof obj1[key] !== typeof obj2[key]) continue
        // when both are number, set the property to the difference of the two
        if (typeof obj1[key] === 'number') {
            // only use different numbers
            if (obj1[key] !== obj2[key]) {
                result[key] = obj2[key] - obj1[key]
                // in case you really wanted the "diff" property
                // result.diff = obj2[key] - obj1[key]
            }
        }
        // recursively iterate over each member of array
        else if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) {
            result[key] = obj1[key].map((item, index) => diffObject(item, obj2[key][index]))
        }
        // recursively iterate objects
        else if (typeof obj1[key] === 'object' && obj1[key]) {
            result[key] = diffObject(obj1[key], obj2[key])
        }
        // copy equal data
        else if (obj1[key] === obj2[key]) {
            result[key] = obj1[key]
        }
        // anything else is ignored
    }
    return result
}

console.log(diffObject(val1, val2))

There are edge cases that this cannot solve, such as recursive structures or non-plain objects.

Also, in your desired output, you keep using diff property, but in some cases, the objects have two numeric properties, so it would be overriden, I kept the original property name instead.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jakub Kotrs