I have a multi step javascript object like this:
const source = {
prev: [],
current: {
list: [{ a: 1, b: 2, c: 3 }],
data: [{ c: 1, b: 2 }]
},
id: 12,
next: []
};
and I would like to create a fresh copy of it.
I know if I use something like let copy = { ...source }; it's like a shallow copy. So if i change those arrays or objects in source they will also change in copy.
Is there anyway that by de-structuring, I get a fresh copy of all items? or should I do it manually for each and every level deep like this:
copy.prev = [...source.prev]
copy.current.list = [...source.current.list]
copy.current.data= [...source.current.data]
and so on
There's an old way to do it, but still works:
var cloned = JSON.parse(JSON.stringify(original));
You can use JSON.stringify
const source = {
prev: [],
current: {
list: [{ a: 1, b: 2, c: 3 }],
data: [{ c: 1, b: 2 }]
},
id: 12,
next: []
};
newObj = JSON.parse(JSON.stringify(source));
You can deconstruct like this:
const source = {
prev: [],
current: {
list: [{ a: 1, b: 2, c: 3 }],
data: [{ c: 1, b: 2 }]
},
id: 12,
next: []
};
const {
current: {list: listClone, data: dataClone}
} = source
console.log(listClone, dataClone)
With a simple object containing objects, arrays, and primitives, it's not that much code just to recursively clone everything.
Just return the primitives, map() the arrays, and reduce the object keys passing the values back in. Something like:
const source = {prev: [],current: {list: [{ a: 1, b: 2, c: 3 }],data: [{ c: 1, b: 2 }]},id: 12,next: []};
function clone(obj){
return (typeof obj == 'object')
? (Array.isArray(obj))
? obj.map(i => clone(i))
: Object.entries(obj).reduce((o, [key, value]) =>
Object.assign(o, {[key]: clone(value)}), {})
: obj
}
let a_clone = clone(source)
console.log(JSON.stringify(a_clone, null, 2))
// is the clone deep? should be false
console.log(a_clone.prev === source.prev)
console.log(a_clone.current.list === source.current.list)
Related
I have an array of object as below.
data: [ {col: ['amb', 1, 2],} , {col: ['bfg', 3, 4], },]
From above, I need to get array of array as below.
[ [{a: 'amb',b: [1], c: 'red'}, {a: 'amb',b: [2], c: 'orange'}],
[{a: 'bfg',b: [3], c: 'red'}, {a: 'bfg',b: [4], c: 'orange'}]
]
My attempt is as below.
let arrInner: Array<any> = []
let arrOuter: Array<Array<any>> = []
_.forEach(data, (item, i) => {
//create two objects redObj and orangeObj
redObj = {
a: item.col[0].toString(),
b: [item.col[1] as number],
c: 'red'
}
orangeObj = {
a: item.col[0].toString(),
b: [item.col[2] as number],
c: 'orange'
}
//put those two objects to array
arrInner.push(redObj)
arrInner.push(orangeObj)
//assign that array to another array
arrOuter[i] = arrInner
})
But when I print the arrOuter, it is not my expected output. Where I was wrong and how can I fix this?
You need to create a new arrInner each time through the forEach loop. Then push that onto arrOuter.
let arrOuter: Array <Array <any>> = []
_.forEach(data, (item, i) => {
//create two objects redObj and orangeObj
redObj = {
a: item.col[0].toString(),
b: [item.col[1] as number],
c: 'red'
}
orangeObj = {
a: item.col[0].toString(),
b: [item.col[2] as number],
c: 'orange'
}
//put those two objects to array
let arrInner = [redObj, orangeObj]
//assign that array to another array
arrOuter.push(arrInner)
})
I have an object like this:
const myObj = {
a: {
b: {
c: 1,
d: 2
},
f: {
z: 4,
u: 6
}
}
}
into this:
const myObj = [
{
c: 1,
d: 2,
},
{
z: 4,
u: 6,
}
]
I found this: How to recursively transform an array of nested objects into array of flat objects? but the original is an array of objects, and mine is an object itself.
You can traverse the values of the objects until you reach the leaves (objects with no values that are other objects).
const myObj = {
a: {
b: {
c: 1,
d: 2
},
f: {
z: 4,
u: 6
}
}
};
const flatObj = o => Object.values(o).some(x => x === Object(x)) ?
Object.values(o).flatMap(flatObj) : [o];
console.log(flatObj(myObj))
I am trying to remove these 2 properties from each object in the array while it's not working. Each object is inside an array. I mean The main array contains arrays of objects. Is there an easy way to solve it without using the map() twice? And how to return the main modified array in that case?
const modifiedItems = this.items.map(item => {
delete item.created,
delete item.removed
return item
})
Data looks like this:
this.items = [
[{ removed: 1, created: 1, a:2, b: 2, c: 3}]
[{ removed: 1, created: 1, a:2, b: 2, c: 3}, { removed: 1, created: 1, a:2, b: 2, c: 3},]
];
The above code doesn't work because it doesn't map to arrays inside the main array. How is the best to delete those properties from each object of all arrays located in the main array?
Instead of deleting, why not just return an object without the properties you want to remove.
You could destructure the properties you want to remove and then collect other properties in a variable using the rest parameters syntax. After this, you just need to return the variable which contains all the properties other than the ones you want to remove.
const modifiedItems = this.items.map(
({ created, removed, ...rest }) => rest
);
Following code snippet shows an example:
const arr = [
{ removed: 1, created: 1, a:2, b: 2, c: 3},
{ removed: 1, created: 1, a:2, b: 2, c: 3},
{ removed: 1, created: 1, a:2, b: 2, c: 3},
];
const modifiedItems = arr.map(
({ created, removed, ...rest }) => rest
);
console.log(modifiedItems);
Edit:
In your case, this.items is an array that contains nested arrays. So to remove the properties from the objects inside the nested arrays, you need to map over each nested array as well.
Following code snippet shows an example:
const items = [
[ { removed: 1, created: 1, a: 2, b: 2, c: 3 } ],
[
{ removed: 1, created: 1, a: 2, b: 2, c: 3 },
{ removed: 1, created: 1, a: 2, b: 2, c: 3}
]
];
const modifiedItems = items.map(subArr => {
return subArr.map(({ created, removed, ...rest }) => rest)
});
console.log(modifiedItems);
Say I have an array of 3 objects like this:
[
{
a: 4,
b: 5,
c: 4
},
{
a: 3,
b: 5,
c: 6
},
{
a: 2,
b: 3,
c: 3
}
]
I would like to return an array of arrays containing the objects that share a common value for the property b. So the resulting array would contain only one array containing 2 objects like this:
[
[
{
a: 4,
b: 5,
c: 4
},
{
a: 3,
b: 5,
c: 6
}
]
]
How would I do this?
You could do this with map and filter
var data = [{"a":4,"b":5,"c":4},{"a":3,"b":5,"c":6},{"a":2,"b":3,"c":3}];
var check = data.map(e => {return e.b});
var result = [data.filter(e => { return check.indexOf(e.b) != check.lastIndexOf(e.b)})];
console.log(result)
To group multiple objects in separate arrays with same b values you can use map and forEach
var data = [{"a":4,"b":5,"c":4},{"a":3,"b":5,"c":6},{"a":2,"b":3,"c":3}, {"a":3,"b":7,"c":6},{"a":2,"b":7,"c":3}], result = [];
var check = data.map(e => {return e.b});
data.forEach(function(e) {
if(check.indexOf(e.b) != check.lastIndexOf(e.b) && !this[e.b]) {
this[e.b] = [];
result.push(this[e.b]);
}
(this[e.b] || []).push(e);
}, {});
console.log(result)
This proposal uses a single loop with Array#forEach but without Array#indexOf.
var array = [{ a: 4, b: 5, c: 4 }, { a: 3, b: 5, c: 6 }, { a: 2, b: 3, c: 3 }],
grouped = [];
array.forEach(function (a) {
this[a.b] = this[a.b] || [];
this[a.b].push(a);
this[a.b].length === 2 && grouped.push(this[a.b]);
}, Object.create(null));
console.log(grouped);
You can create a function that accepts fulfillment criteria and will return as many nested arrays as rules passed.
Let's say you have an array of objects, arr.
var arr = [{a: 1, b: 2}, {a: 3, b: 2}, {a: 3, b: 4}, {a: 1, b: 1}]
And you want to return an array with with nested arrays that fulfill a particular requirement, let's say you want objects with an a:1 and b:2.
You can create a function that loops through your rules and creates a nested array with the objects that fulfill each rule.
For example:
var arr = [{a: 1, b: 2}, {a: 3, b: 2}, {a: 3, b: 4}, {a: 1, b: 1}]
function makeNestedArrays() {
var rules = [].slice.call(arguments);
return rules.reduce(function(acc, fn) {
var nestedArr = [];
arr.forEach(function(obj) {
if (fn(obj)) {
nestedArr.push(obj);
}
});
// only push nested array
// if there are matches
if (nestedArr.length) {
acc.push(nestedArr);
}
return acc;
}, []);
}
var result = makeNestedArrays(
function(obj) { return obj.a === 1; },
function(obj) { return obj.b === 2; }
);
console.log(result);
This allows you to pass as many "rules" as you want, and will create a nested array for each rule so long as there is at least one match.
You could use a Map to group them, this should work with any kind of value (just be sure the equality rules check out):
var arr = [{
a: 4,
b: 5,
c: 4
}, {
a: 3,
b: 5,
c: 6
}, {
a: 2,
b: 3,
c: 3
}];
var result = arr.reduce(function(m, o){
var value = o.b;
if(m.has(value)){
m.get(value).push(o);
} else {
m.set(value, [o]);
}
return m;
}, new Map());
console.log(...(result.values()));
If you'd need to filter out the groups of 1:
var arr = [{
a: 4,
b: 5,
c: 4
}, {
a: 3,
b: 5,
c: 6
}, {
a: 2,
b: 3,
c: 3
}];
var result = arr.reduce(function(m, o){
var value = o.b;
if(m.has(value)){
m.get(value).push(o);
} else {
m.set(value, [o]);
}
return m;
}, new Map());
result = [...result.values()].filter(a => a.length > 1);
console.log(result);
I'm trying to validate that an array of objects like this:
[
{
a: 1,
b: 2,
c: 3
},
{
a: 4,
b: 5,
c: 6
},
...
]
contains at least one object with both { a: 1 } and { c: 3 }:
I thought I could do this with chai-things, but I don't know all the properties of the object to be able to use
expect(array).to.include.something.that.deep.equals({ ??, a: 1, c: 3});
and contain.a.thing.with.property doesn't work with multiple properties :/
What's the best way to test something like this?
The desired solution seems to be something like:
expect(array).to.include.something.that.includes({a: 1, c: 3});
I.e. array contains an item which includes those properties. Unfortunately, it appears to not be supported by chai-things at the moment. For the foreseeable future.
After a number of different attempts, I've found that converting the original array makes the task easier. This should work without additional libraries:
// Get items that interest us/remove items that don't.
const simplifiedArray = array.map(x => ({a: x.a, c: x.c}));
// Now we can do a simple comparison.
expect(simplifiedArray).to.deep.include({a: 1, c: 3});
This also allows you to check for several objects at the same time (my use case).
expect(simplifiedArray).to.include.deep.members([{
a: 1,
c: 3
}, {
a: 3,
c: 5
}]);
Most elegant solution I could come up with (with the help of lodash):
expect(_.some(array, { 'a': 1, 'c': 3 })).to.be.true;
Most straight forward way I can think of is:
const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-like'));
chai.use(require('chai-things'));
expect([
{
a: 1,
b: 2,
c: 3,
},
{
a: 4,
b: 5,
c: 6,
},
]).to.be.an('array').that.contains.something.like({ a: 1, c: 3 });
Seems like the chai-subset plugin from chai seems to have done the trick. Here is something I have working:
const chai = require('chai');
const chaiSubset = require('chai-subset');
chai.use(chaiSubset);
const expect = chai.expect;
expect([ { type: 'text',
id: 'name',
name: 'name',
placeholder: 'John Smith',
required: 'required' },
{ type: 'email',
id: 'email',
name: 'email',
placeholder: 'example#gmail.com',
required: 'required' },
{ type: 'submit' } ]).containSubset([{ type: 'text', type: 'email', type: 'submit' }]);
Solution without third libraries or plugins:
let array = [
{ a: 1, b: 2, c: 3 },
{ a: 4, b: 5, c: 6 }
];
let match = array.map(({a, c}) => ({a, c})).to.deep.include({ a:1, c:3});
You could write your own function to test the array. In this example you pass in the array and the object containing the relevant key/value pairs:
function deepContains(arr, search) {
// first grab the keys from the search object
// we'll use the length later to check to see if we can
// break out of the loop
var searchKeys = Object.keys(search);
// loop over the array, setting our check variable to 0
for (var check = 0, i = 0; i < arr.length; i++) {
var el = arr[i], keys = Object.keys(el);
// loop over each array object's keys
for (var ii = 0; ii < keys.length; ii++) {
var key = keys[ii];
// if there is a corresponding key/value pair in the
// search object increment the check variable
if (search[key] && search[key] === el[key]) check++;
}
// if we have found an object that contains a match
// for the search object return from the function, otherwise
// iterate again
if (check === searchKeys.length) return true;
}
return false;
}
deepContains(data, { a: 4, c: 6 }); // true
deepContains(data, { a: 1, c: 6 }); // false
DEMO