Search code examples
javascripttypescriptobjectrecursionjavascript-objects

Deep comparison of two objects of same type but different structure


I'm trying to compare two objects of the same type. What I want to achieve at the end, is a new object with only the properties that are different.

The solution implemented so far, with some previous help in another post, will, in fact, give me back an object with the properties that have changed, but, if the properties in inner objects are not in the same order, which happens since the ordering of object properties is non-standard in ECMAScript, I will also get those in the final object which is not what I want.

In the example below:

z returns

{
  "properties": {
    "shared": false,
    "query": "project!=\"JP\""
  }
} 

whilst I would want z to be:

{
  "properties": {
    "query": "project!=\"JP\""
  }
} 

since the only property different between the two objects is query

type X = {
        owner: {
            accountId: string,
            displayName: string
        },
        filter: {
            id: string,
            name: string,
        },
        properties: {
            id: string,
            query: string
            shared: boolean
            syncDate?: number
        }
    }


const a: X = {
    filter: {
        id: "10021",
        name: "fil"
    },
    owner: {
    accountId: "61498eeaa995ad0073bb8444",
    displayName: "Jorge Guerreiro"

    },
    properties: {
        id: "10021",
        query: 'project!="JP"',
        shared: false,
        syncDate: undefined
    }
}


const b: X = {
    filter: {
        id: "10021",
        name: "fil"
    },
    owner: {
    accountId: "61498eeaa995ad0073bb8444",
    displayName: "Jorge Guerreiro"

    },
    properties: {
        id: "10021",
        shared: false,
        query: 'project="JP"',
        syncDate: undefined
    }
}

const deepCompare = <T>(oldFilter: T, newFilter:T): Partial<T> => {
    return Object.values(oldFilter).reduce((bef, aft, i) => {
        const valueB = Object.values(newFilter)[i];
        const keyB = Object.keys(newFilter)[i];
        if (valueB instanceof Object && keyB) {
            const delta = deepCompare(valueB, aft);
            return Object.keys(delta).length > 0 ? { [keyB]: deepCompare(valueB, aft), ...bef } : bef;
        }
        return valueB !== aft && keyB ? { [keyB]: valueB, ...bef } : { ...bef };
    }, {});
}

const z = deepCompare<X>(a, b)

I'm a bit stuck on how to have the recursive function below do what I want. Any help would be great.

Thanks


Solution

  • sorry got home pretty late, here's a typed version that works, posting as I've tested it

    // pretty simple complicated as we're dealing with the same object type
    type Accumulator<T> = { [k in keyof T]?: T[k] | {} }
    
    // 1. just T =)
    const deepCompare = <T extends object>(oldFilter: T, newFilter: T): Accumulator<T> => {
    
       // 3. here though a different generic for varying props
       const traverse = <O>(obj: O, filter: O, target: Accumulator<O> = {}): Accumulator<O> => {
    
         for (let k in obj)
           if (obj[k] instanceof Object && filter[k]) {
    
             target[k] = {}     // 4. O[k] for the next prop type
             let targetResult = traverse<O[typeof k]>(obj[k], filter[k], target[k])
    
             // delete empty entries if so desired
             if (!Object.keys(targetResult).length)
               delete target[k]
           }
           else if (obj[k] !== filter[k])
             target[k] = obj[k] // store value
    
           return target
       }
    
       // 2. still T on first recursion call
       return traverse<T>(oldFilter, newFilter)
    }
    
    deepCompare({a: {v:'fv', g: 'ghdg', h: 'kfn', k: {l: '2222' }}},{a: {v:'fv', g: '1111', h: 'kfn', k: {l: 'dfg' }}})
    

    that is all