Home > OS >  Deep comparison of two objects of same type but different structure
Deep comparison of two objects of same type but different structure

Time:08-24

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

CodePudding user response:

try this

// scope to return the end result
const deepCompare = <T>(oldFilter: T, newFilter:T): Partial<T> => {
    // resursive function where the target argument 
    // will be the filtered result of the current entry
    const traverse = (obj: any, filter: any, target: any = {}) => {
      // looping entries by key is the most performant way to
      // iterate through obj properties
      for (let k in obj)
        if (obj[k] instanceof Object && filter[k]) {
          // ad level, go deeper
          target[k] = {}
          let targetResult = traverse(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
    }

    return traverse(oldFilter, newFilter)
}

deepCompare(a,b)

for typing you need to make your objects indexable so that typescript doesnt complain, I usually do generic keyof typeof T[k] or infer the nested agruments

  • Related