Reputation: 847
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
Upvotes: 1
Views: 1371
Reputation: 833
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
Upvotes: 1
Reputation: 833
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
Upvotes: 1