Object.assign(...as) appears to change the input parameter. Example:
const as = [{a:1}, {b:2}, {c:3}];
const aObj = Object.assign(...as);
I deconstruct an array of object literals as parameter of the assign function.
I omitted console.log statements. Here's the stdout from node 13.7:
as before assign: [ { a: 1 }, { b: 2 }, { c: 3 } ]
aObj: { a: 1, b: 2, c: 3 }
as after assign: [ { a: 1, b: 2, c: 3 }, { b: 2 }, { c: 3 } ]
The reader may notice that as first element has been changed in an entire.
Changing a new array bs elements to an immutable object (using freeze)
const bs = [{a:1}, {b:2}, {c:3}];
[0, 1, 2].map(k => Object.freeze(bs[k]));
const bObj = Object.assign(...bs);
leads to an error:
TypeError: Cannot add property b, object is not extensible
at Function.assign (<anonymous>)
Which indicates the argument is indeed being changed.
What really confounds me is that even binding my array, cs, by currying it to a function (I think you call this a closure in JS)
const cs = [{a:1}, {b:2}, {c:3}];
const f = (xs) => Object.assign(...xs);
const g = () => f(cs);
const cObj = g();
returns:
cs before assign: [ { a: 1 }, { b: 2 }, { c: 3 } ]
cObj: { a: 1, b: 2, c: 3 }
cs after assign: [ { a: 1, b: 2, c: 3 }, { b: 2 }, { c: 3 } ]
What went wrong here? And how may one safely use Object.assign without wrecking its first argument?
Object.assign is not a pure function, it writes over its first argument target.
Here is its entry on MDN:
Object.assign(target, ...sources)
Parameters
target
The target object — what to apply the sources’ properties to, which is returned after it is modified.
sources
The source object(s) — objects containing the properties you want to apply.
Return value
The target object.
The key phrase is "[the target] is returned after it is modified". To avoid this, pass an empty object literal {} as first argument:
const aObj = Object.assign({}, ...as);
Related
I have two arrays; one with substrings and the other with objects.
I would like to obtain an array of objects where those objects contain any of the substrings in the substrings array.
So far I have tried to use filter and findIndex. Each approach works if a substring is identical. In this case, even indexOf was not working. I am not javascript guy, so probably I am doing something wrong.
Script
var strings = ['12', 'sv', 'eli', '23', '34'];
var data = [
{
a: 349531284734,
b: "sv123eippppppeli",
c: "aaabbbccc"
},
{
a: 1111123333312,
b: "ccccccccccccs2222",
c: "aaabbbccc"
},
{
a: 2222234,
b: "elllllllla",
c: false
},
];
// attempt 1
var results = data.filter(arr =>
Object.keys(arr).some(key => {
return String(arr[key]).toLowerCase().includes(strings) // or indexOf
})
);
// attempt 2 with only one data index
var obj = Object.values(data[0]);
var results = strings.some(s => obj.includes(s)) // or indexOf or findIndex;
Explanation
In this example with the given substrings, only data[0] is a match because it contains at least on of the substrings in the strings array.
How can I make this work without using a "for loop"?
Thanks
A simple solution that avoids the need for "for-loop" syntax would be to filter() each object of data by a predicate that checks if any value (of the current item being filtered) contains any one of the values of the strings array.
In code, this can be expressed as:
var strings = ['12', 'sv', 'eli', '23', '34'];
var data = [{
a: 349531284734,
b: "sv123eippppppeli",
c: "aaabbbccc"
},
{
a: 1111123333312,
b: "ccccccccccccs2222",
c: "aaabbbccc"
},
{
a: 2222234,
b: "elllllllla",
c: false
},
];
// Local helper retruns true if case-insenstive value in strings array
const stringHasValue = (value) => strings
.some(str => value.toLowerCase().includes(str.toLowerCase()))
// Filter each item in data array by existance of a value containing
// substring in strings array
var result = data.filter((item) => Object.values(item)
.filter(value => typeof value === 'string')
.some(stringHasValue, []));
console.log(result);
For example I want something like:
{
a: 1,
b: 2,
c: 3
}
turned into:
{
d: {
a: 1,
b: 2,
c: 3
}
}
I've tried assigning a new property to that object with the object itself but it shows up as circular so I figure it's a reference instead of the actual properties instead of the actual values. I want to try something like JSON.stringify the object and assign it to the property but I don't know how to turn that string into an object format that I can assign to the property.
let firstObj = {
a: 1,
b: 2,
c: 3
}
let secondObj = {};
secondObj.d = firstObj;
console.log(secondObj);
Basically you create a new object and assign the original object to its property d.
You can use ES6 destructuting to make a shallow copy of the object and put it on a new prop:
let obj = {
a: 1,
b: 2,
c: 3
}
obj.d = {...obj}
console.log(obj)
If that's not an option you can reduce() over the objects keys to make a new object and assign it to d:
let obj = {
a: 1,
b: 2,
c: 3
}
obj.d = Object.keys(obj).reduce((newObj, k) => {
newObj[k] = obj[k]
return newObj
},{})
console.log(obj)
It depends whether you want to make the deep or shallow copy of the object d. (Can the object d have a nested structure?)
The question about efficient ways to clone the object has already been answered here.
I have an object with several nested layers of arrays and subobjects, from which I need to extract the values from some paths. Is there some library or native function which can help me do that? I'm already using Lodash and jQuery, but have a hard time figuring out how to simplify this problem.
Example:
{
a: [
{
b: 0,
c: 1
},
{
b: 1,
c: 2
}
]
}
Now I would like to get a list of all a[0..n].b.
My actual object is much larger and has 3 layers of arrays and a path like syn[0].sem[0].pdtb3_relation[0].sense, so I'd rather not write 3 nested for loops if a library function exists.
You can use forEach() to iterate through array.
var o = {
a: [
{
b: 0,
c: 1
},
{
b: 1,
c: 2
}
]
}
Object.keys(o).forEach(a => o[a].forEach(y => console.log(y.b)));
First of all, I'm fairly new to streams, so I'm still getting to grips with some common patterns.
In many libraries we can split a stream into a stream of streams using .groupBy(keySelectorFn). For example, this stream is split into streams based on the value of 'a' in each object (pseudo-code, not based on any particular library):
var groups = Stream.of(
{ a: 1, b: 0 },
{ a: 1, b: 1 },
{ a: 2, b: 2 },
{ a: 1, b: 3 }
)
.groupBy(get('a'));
Say I want to process groups differently based on the value of 'a' of that group:
groups.map(function(group) {
if (..?) {
// Run the group through some process
}
return group;
});
I can't see how to get the value of 'a' without consuming the first element of each group (and if the first element of a group is consumed the group is no longer intact).
This seems to me a fairly common thing that I want to do with streams. Am I taking the wrong approach?
--- EDIT ---
Here's a more specific example of a problem that I'm stuck on:
var groups = Stream.of(
{ a: 1, b: 0 },
{ a: 1, b: 1 },
{ a: 2, b: 0 },
{ a: 2, b: 1 },
{ a: 2, b: 2 },
{ a: 1, b: 2 }
)
.groupBy(get('a'));
How to select the first 1 object where a === 1, and the first 2 objects where a === 2, and pass any other objects straight through? This seems logical to me:
groups.chain(function(group) {
return group.key === 1 ?
group.take(1) :
group.key === 2 ?
group.take(2) :
group ;
});
But group.key does not exist (and even if it did it would seem a bit... smelly).
groupBy will give you a stream of streams(each value in the stream being a stream itself). using fold, you can process each group (which is a stream), into a single value (using conditionals). flatMap puts all the results into a single stream. Here is a simple example that processes groups of objects. It groups the objects according to property "a", does a arithmetic operation based on the value into a single object containing type and val properties. These final objects are flattened into a single stream:
var groupStream = Bacon.fromArray([
{ a: 1, b: 0 },
{ a: 1, b: 1 },
{ a: 2, b: 2 },
{ a: 1, b: 3 }
]);
// -----[stream of objects where a=1]-------[stream of objects where a=2]---->
var groups = groupStream.groupBy(function(k){ return k.a; })
// v is a stream of a=1 objects or a=2 objects
groups.flatMap(function(v) {
//fold(reduce) over the values in v (stream)
return v.fold({type:'', val: 0},function(acc,i) {
if(i.a == 1) {
return {type: 'one', val: acc.val + i.b };
}
if(i.a == 2) {
return {type: 'two', val: acc.val + i.b };
}
})
}).onValue(function(v) {
console.log(v);
});
Here is the jsbin: http://jsbin.com/haliqerita/edit?js,console
Hope that helps.
Suppose my my reactjs component has two states:
a: {
a: 1
},
b: [1, 2, 3]
Now I want them to become:
a: {
a: 1,
b: true
},
b: [1, 2, 3, 4]
Is it correct to do it by:
this.state.a.b = true;
b = this.state.b.push(4);
this.setState({
a: this.state.a,
b: b
});
If not, what is the appropriate way to do it.
Best way to do it.
this.setState({
a: Object.assign({}, this.state.a, { b: true }),
b: [...this.state.b, 4]
});
The state properties should be replaced, and not mutated:
this.setState({
a: { // create new a object
a: this.state.a.a,
b: true
},
b: this.state.b.concat([4]) // create new b array
});
Straight from the docs:
Do Not Modify State Directly
For example, this will not re-render a component:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
So it is not necessary to copy an object before modifying it, if you use setState
So your code might look like this:
this.setState({
a: (this.state.a.b = true),
b: this.state.b.push(4)
})