'Compare object structure
I have an object like this:
const obj = {
name: "one",
created: "2022-05-05T00:00:00.0Z",
something: {
here: "yes"
}
}
I can check if it has certain attributes one by one:
assert( obj.name !== undefined && obj.name === "one")
assert( obj.something !== undefined && obj.something.here !== undefined && obj.something.here === "yes")
Is there a way to achieve the same passing another object to compare? The other object would have less values (in this example I don't care if the created
attribute is present)
Something like:
const obj = {
name: "one",
created: "2022-05-05T00:00:00.0Z",
something: {
here: "yes"
}
}
hasAllOf(obj, {
name: "one",
something: {
here: "yes"
}
}) // returns true
Either with lodash or some other library?
Solution 1:[1]
You can write recursive hasAllOf
with a type-checking helper, is
-
const is = (t, T) =>
t?.constructor === T
const hasAllOf = (a, b) =>
is(a, Object) && is(b, Object) || is(a, Array) && is(b, Array)
? Object.keys(b).every(k => hasAllOf(a[k], b[k]))
: a === b
const obj = {
name: "one",
created: "2022-05-05T00:00:00.0Z",
something: {
here: "yes",
and: ["this", 2, { f: "three" }]
}
}
console.log(
hasAllOf(obj, {
name: "one",
something: {
here: "yes",
and: ["this", 2, { f: "three" }]
}
})
)
Support Map
and Set
by using another conditional
const is = (t, T) =>
t?.constructor === T
const hasAllOf = (a, b) => {
switch (true) {
case is(a, Object) && is(b, Object) || is(a, Array) && is(b, Array):
return Object.keys(b).every(k => hasAllOf(a[k], b[k]))
case is(a, Set) && is(b, Set):
return [...b].every(v => a.has(v))
case is(a, Map) && is(b, Map):
return [...b.keys()].every(k => hasAllOf(a.get(k), b.get(k)))
default:
return a === b
}
}
const obj = {
name: new Map([[1, "one"], [2, "two"]]),
created: "2022-05-05T00:00:00.0Z",
something: {
here: new Set("yes", "ok"),
and: ["this", 2, { f: "three" }],
}
}
console.log(
hasAllOf(obj, {
name: new Map([[1, "one"]]),
something: {
here: new Set("yes"),
and: ["this", 2, { f: "three" }]
}
})
)
Solution 2:[2]
The lodash isEqual function may be what you are looking for.. https://lodash.com/docs/#isEqual
const obj = {
name: "one",
created: "2022-05-05T00:00:00.0Z",
something: {
here: "yes"
}
}
_.isEqual(delete obj.created, delete {
name: "one",
created: "2022-05-05T00:00:00.0Z",
something: {
here: "yes"
}}.created); //returns true
or
const obj = {
name: "one",
created: "2022-05-05T00:00:00.0Z",
something: {
here: "yes"
}
}
delete obj.created;
_.isEqual(obj, {
name: "one",
something: {
here: "yes"
}}.created); //returns true
You could even use a different lodash function (_.cloneDeep
) to copy the original so you don't mutate it.
Solution 3:[3]
function hasAllOf(obj,data) {
let obj_keys = Object.keys(obj)
for (let key in data) {
if (!(obj_keys.includes(key))) {
return false;
}
if (typeof data[key] === "object" && typeof obj[key] === "object")
{
let res = hasAllOf(obj[key], data[key])
if (!res) {
return res;
}
res = hasAllOf(data[key], obj[key])
if (!res) {
return res;
}
}
else if (data[key] !== obj[key])
{
return false;
}
}
return true;
}
const obj =
{
name: "one",
created: "2022-05-05T00:00:00.0Z",
something:
{
here: "yes",
}
}
const data =
{
name: "one",
something:
{
here: "yes",
},
}
console.log(hasAllOf(obj, data))
Solution 4:[4]
I think _.conformsTo matches closely what I'm looking for, it's a bit verbose, but much better and legible than doing all the comparisons one by one (specially in larger objects)
_.conformsTo(obj, {
name: (n) => n === "one",
created: (c) => _.isString(c),
// calling _conformsTo on nested objects
something: (s) => _.conformsTo(s, {
here: (h) => h === "yes"
})
})
Update...
I can even create a is
and conformsTo
functions that returns a "partially applied" function and make it more legible.
Full example:
const _ = require('lodash')
const is = (expected) => (actual) => actual === expected
const conformsTo = (template) => (actual) => _.conformsTo(actual, template)
_.conformsTo(
{ // source
name: "one",
created: "2022-05-05T00:00:00.0Z",
something: {
here: "yes"
}
},
{ // "spec"
name: is('one'),
something: conformsTo({
here: is("yes")
})
}
)
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 | |
Solution 2 | |
Solution 3 | MoRe |
Solution 4 |