I am trying to find a library able to say if an object B includes all property and values of an object A (and not the opposite !).
It means 2 things that the librairies i found do not handle:
The object B could have more keys than object A, and it could be
true.
The object B could have more items in its arrays than object
A, and it could be true.
This is basically what the method _.difference() of lodash does, but only for items in arrays.
i found interesting libraries like deep-diff, but anything for my need.
I could code something doing the job, but i am convinced other peoples met this need before.
Here is an example with 2 objects:
var A = {
name: 'toto',
pets: [ { name: 'woof', kind: 'dog' } ]
};
var B = {
name: 'toto',
pets: [ { name: 'gulp', kind: 'fish' }, { name: 'woof', kind: 'dog' } ],
favoriteColor: 'blue'
};
Here, A includes B since we can find in B every properties and values of A.
But librairies like diff would say no, since this is not the first but the second item of "pets" which is equal to A, and B has an additionnal property "favoriteColor".
Do you know a librairy able to do this kind of comparison?
You could use a modified version of the deepCompare that was linked in the comments. Really you just need to get past the keys length comparison, it seems.
// where b has all of a
function deepHas(a, b) {
if (typeof a !== typeof b) {
return false;
}
if (typeof a !== 'object') {
return a === b;
}
// you may need to polyfill array higher-order functions here
if(Array.isArray(a) && Array.isArray(b)) {
return a.every(function(av) {
return b.indexOf(av) !== -1;
});
}
if (Object.keys(a).length > Object.keys(b).length) {
return false;
}
for (var k in a) {
if (!(k in b)) {
return false;
}
if (!deepHas(a[k], b[k])) {
return false;
}
}
return true;
}
var a1 = {
foo: ['a', 'b', 'c'],
bar: 'bar',
baz: {
baz: {
baz: 'wee'
}
}
};
var b1 = {
foo: ['a', 'b', 'c', 'd'],
bar: 'bar',
baz: {
baz: {
baz: 'wee',
whatever: 'wat'
}
},
ok: 'test'
};
console.log('b1 has all of a1', deepHas(a1, b1));
var a2 = {
foo: ['a', 'b', 'c'],
bar: 'bar',
baz: {
baz: {
baz: 'wee'
}
}
};
var b2 = {
foo: ['a', 'b', 'c'],
baz: {
baz: {
baz: 'wee'
}
}
};
console.log('b2 does not have all of a2', !deepHas(a2, b2));
console.log('["a","b"] has ["b"]', deepHas(["b"], ["a","b"]));
console.log('{foo: ["a","b"]} has {foo: ["b"]}', deepHas({ foo: ["b"] }, { foo:["a","b"] }));
Related
how can i return an array of objects taking from array of again one more level array. I am using push.
is there any better way to achieve this
let a = [{b: [{c: "k"}]}]
let o = []
a.forEach(so => {so.b.forEach(obc => o.push(obc))})
console.log(o)
I'd use flatMap() instead:
const a = [{
b: [{
foo: 'foo'
}]
},
{
b: [{
c: "k"
},
{
bar: 'bar'
}
]
}
];
const o = a.flatMap(({ b }) => b);
console.log(o);
(but this is a relatively new method, so if you want to use it and still support older environments, be sure to include a polyfill)
Lacking that, you can also improve your existing code by using concat() with the inner array instead of iterating over each inner item:
const a = [{
b: [{
foo: 'foo'
}]
},
{
b: [{
c: "k"
},
{
bar: 'bar'
}
]
}
];
let o = [];
a.forEach(({ b }) => {
o = o.concat(b);
});
console.log(o);
Try
let a = [{b: [{c: "k"}]}]
let o =a[0].b
console.log(o)
I'm fetching data from a soccer live API every minute using AWS for automation. I'd like to compare the new data with the old one. Each piece of data is an Array, so two JavaScript Arrays will be compared.
If there is a change in a new value, this value will be shown to the user at the Vue.js front-end. For example, the user will be alerted when shot on target rises to 4 from 3.
However, I'm struggling with that algorithm. Which libraries or functions will solve my problem?
It is more like you want to know the delta between two exactly same javascript structures. When I want to find out delta I generally use deep-object-diff. It has multiple difference functions.
From the example from npmjs:
const lhs = {
foo: {
bar: {
a: ['a', 'b'],
b: 2,
c: ['x', 'y'],
e: 100 // deleted
}
},
buzz: 'world'
};
const rhs = {
foo: {
bar: {
a: ['a'], // index 1 ('b') deleted
b: 2, // unchanged
c: ['x', 'y', 'z'], // 'z' added
d: 'Hello, world!' // added
}
},
buzz: 'fizz' // updated
};
console.log(detailedDiff(lhs, rhs));
/*
{
added: {
foo: {
bar: {
c: {
'2': 'z'
},
d: 'Hello, world!'
}
}
},
deleted: {
foo: {
bar: {
a: {
'1': undefined
},
e: undefined
}
}
},
updated: {
buzz: 'fizz'
}
}
*/
i know there has many answer for unique array
but they can't handle with array of array
what i want is
source array
[
1,
0,
true,
undefined,
null,
false,
['a', 'b', 'c'],
['a', 'b', 'c'],
['a', 'c', 'b'],
{ a: { b: 2 } },
{ a: { b: 2 } },
{ a: { b: 3 } },
{ a: { b: undefined } },
{ a: { } },
{ a: { b: 3, c: undefined } },
]
the return
[
1,
0,
true,
undefined,
null,
false,
['a', 'b', 'c'],
['a', 'c', 'b'],
{ a: { b: 2 } },
{ a: { b: 3 } },
{ a: { b: undefined } },
{ a: { } },
{ a: { b: 3, c: undefined } },
]
arr-unique can handle object[], but can't handle array of array
Set can't too
fail code
console.log(array_unique(data));
console.log([...new Set(data)]);
console.log(data.filter(function (el, index, arr)
{
return index == arr.indexOf(el);
}));
===================
update
i create a module for this array-hyper-unique, but didn't use json stringify because it has a bug when valuse is regexp
One easy method would be to stringify the arrays and objects in order to identify duplicates:
const input = [
1,
true,
['a', 'b', 'c'],
['a', 'b', 'c'],
{ a: { b: 2 } },
{ a: { b: 2 } },
{ a: { b: 3 } },
{ a: { b: 3, c: undefined } },
];
const outputSet = new Set();
const stringifiedObjs = new Set();
input.forEach(item => {
if (typeof item !== 'object') outputSet.add(item);
else {
// replace undefineds with something, else they'll be ignored by JSON.stringify:
const stringified = JSON.stringify(
item,
(k, v) => v === undefined ? 'undefined-value' : v
);
if (!stringifiedObjs.has(stringified)) {
outputSet.add(item);
stringifiedObjs.add(stringified)
}
}
});
console.log([...outputSet]);
Try by converting elements to string using JSON.stringify and use indexOf to push these elements to another array,only if the another array does not contain this element. Then again use map & JSON.parse to convert string to the original format
var data = [
1,
true, ['a', 'b', 'c'],
['a', 'b', 'c'],
{
a: {
b: 2
}
},
{
a: {
b: 2
}
},
{
a: {
b: 3
}
},
]
// Create a new array of only string
// map will give new array and JSON.stringify will convert elements to string
var newData = data.map(function(item) {
return JSON.stringify(item)
})
//An empty array which will contain only unique values
var uniques = [];
// loop over the array of stirngs and check
//if that value is present in uniques array
//if not then push the element
newData.forEach(function(item) {
if (uniques.indexOf(item) === -1) {
uniques.push(item)
}
});
//Convert array of string to json
var parsedArr = uniques.map(function(item) {
return JSON.parse(item)
});
console.log(parsedArr)
The reason you method does not work, is because the first ['a', 'b', 'c'], and the second ['a', 'b', 'c'] are different objects, as are the first and second instances of { a: { b: 2 } }.
Because of this, even though you add them to Set, they will be considered non-equivalent to each other, and therefore, not be filtered for uniqueness.
It seems you want to get a unique array based on the absolute values in each object. One easy way to do this is to use the ES6 Map like so:
function uniq(arr) {
var uniqMap = new Map()
arr.forEach(element => {
uniqMap.set(JSON.stringify(element), element)
})
return [...uniqMap.values()]
}
You can then get the result you are looking for:
uniq(data)
//Result: [ 1, true, [ 'a', 'b', 'c' ], { a: { b: 2 } }, { a: { b: 3 } } ]
You could take a recursive approach for objects and check the values.
function check(a, b) {
if (!a || typeof a !== 'object') {
return a === b;
}
var keys = Object.keys(a);
return keys.length === Object.keys(b).length
&& keys.every(k => k in b && check(a[k], b[k]));
}
var array = [1, 0, true, undefined, null, false, ['a', 'b', 'c'], ['a', 'b', 'c'], ['a', 'c', 'b'], { a: { b: 2 } }, { a: { b: 2 } }, { a: { b: 3 } }, { a: { b: undefined } }, { a: {} }, { a: { b: 3, c: undefined } }],
unique = array.reduce((r, b) => (r.some(a => check(a, b)) || r.push(b), r), []);
console.log(unique);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have this data:
[
{foo: 1, bar: a},
{foo: 2, bar: b},
{foo: 3, bar: c},
]
What's the simplest way to transform the data to something like
{
customLabel1 : [1,2,3],
customLabel2 : [a,b,c]
}
I come up with this
{
customLabel1: data.map((a) => {return a.foo} ),
customLabel2: data.map((a) => {return a.bar} )
}
Is there a simpler way to do this, or faster?
If you want simpler, your code is already pretty close with fat arrow syntax. You can drop the parentheses and the return keyword:
{
customLabel1: data.map(a => a.foo),
customLabel2: data.map(a => a.bar)
}
If you want faster, I think you'll have to sacrifice some simplicity. As it's written, you're looping over data twice. If you iterated once, it would look something like this:
var data = [
{foo: 1, bar: 'a'},
{foo: 2, bar: 'b'},
{foo: 3, bar: 'c'},
];
var o = {customLabel1: [], customLabel2: []};
data.forEach(a => {
o.customLabel1.push(a.foo);
o.customLabel2.push(a.bar);
});
console.log(o);
You can use this form if you dont know the keys
{
customLabel1: data.map(function(element) { return element[Object.keys(element)[0]];}),
customLabel2: data.map(function(element) { return element[Object.keys(element)[1]];})
}
You could use an object for the key mapping and iterate then.
var data = [{ foo: 1, bar: 'a' }, { foo: 2, bar: 'b' }, { foo: 3, bar: 'c' }],
labels = { customLabel1: 'foo', customLabel2: 'bar' },
result = {};
data.forEach(a => Object.keys(labels).forEach(k => {
result[k] = result[k] || [];
result[k].push(a[labels[k]]);
}));
console.log(result);
The shorter syntax of the map call can be:
data.map(el => el.prop);
Having said that, I'd define a helper function for this:
function pluck(arr, props) {
return Object.keys(props).reduce((ret, prop) => {
ret[prop] = arr.map(el => el[props[prop]]);
return ret;
}, {});
}
var ret = pluck(data, {
customLabel1: 'foo',
customLabel2: 'bar'
});
Consider the following function's blueprint which tries to compare two objects:
function objectCompare(a,b,path){
for (var prop in a) {
path=prop;
if (a.hasOwnProperty(prop) && !(b.hasOwnProperty(prop))) {
...
return false;
}
...
if (detectType(a[prop])==='Object'){
if (!objectCompare(a[prop],b[prop],path))
return false;
}
...
}
return true;
}
detectType is my own function which checks the type of a variable. My problem is that I want to enrich variable path every time we have a recursive call. However, at the same time when recursive calls finish, path has to traverse the remaining property names of the initial object without being enriched...
Imagine the following objects:
var Obj1 = {
p1: 's',
p2: {
p1: {a: { propA: 'a', propB: 'b' }},
p2: 'g',
}
};
var Obj2 = {
p1: 's',
p2: {
p1: {a: { propA: 'a', propB: 'c' }},
p2: 'g',
}
};
I want path when function objectComparereturns to have the following value: p2.p1.a.propB Namely, the point which makes the two objects different. How may I achieve that?
You have to add the current key to the path and pass the new path to the recursive call. Consider:
console.info=function(x){document.write('<pre>'+JSON.stringify(x,0,3)+'</pre>')}
//--
// compare: return path if a,b are different, null otherwise
function compare(a, b, path) {
var ta = typeof a,
tb = typeof b;
// different types - fail
if (ta !== tb) {
return path;
}
// scalars - null if equal, path if not
if (ta !== 'object') {
return a === b ? null : path;
}
// make a set of keys from both objects
var keys = Object.keys(a).concat(Object.keys(b)).filter(function(x, i, self) {
return self.indexOf(x) === i;
});
// for each key in set, compare values
var res = null;
keys.some(function(k) {
return res = compare(a[k], b[k], path.concat(k));
});
// return null or the first failed path
return res;
}
//
var Obj1 = {
p1: 's',
p2: {
p1: {a: { propA: 'a', propB: 'b' }},
p2: 'g',
}
};
var Obj2 = {
p1: 's',
p2: {
p1: {a: { propA: 'a', propB: 'c' }},
p2: 'g',
}
};
var res = compare(Obj1, Obj2, [])
console.info(res);
The library deep-diff does what you need.
The above reference presents this example code:
var diff = require('deep-diff').diff;
var lhs = {
name: 'my object',
description: 'it\'s an object!',
details: {
it: 'has',
an: 'array',
with: ['a', 'few', 'elements']
}
};
var rhs = {
name: 'updated object',
description: 'it\'s an object!',
details: {
it: 'has',
an: 'array',
with: ['a', 'few', 'more', 'elements', { than: 'before' }]
}
};
var differences = diff(lhs, rhs);
The code snippet above would result in the following structure describing the differences:
[ { kind: 'E',
path: [ 'name' ],
lhs: 'my object',
rhs: 'updated object' },
{ kind: 'E',
path: [ 'details', 'with', 2 ],
lhs: 'elements',
rhs: 'more' },
{ kind: 'A',
path: [ 'details', 'with' ],
index: 3,
item: { kind: 'N', rhs: 'elements' } },
{ kind: 'A',
path: [ 'details', 'with' ],
index: 4,
item: { kind: 'N', rhs: { than: 'before' } } } ]
Notably the path property is much like your desired output.