Object.assign(...as) changes input parameter - javascript

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

JavaScript: Find in array all values with substrings of array

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);

How do I take all of an object's properties and insert them into its own object array property?

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.

Get list of values from several paths in json object

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)));

How do you group a stream, then process groups separately based on the group's key?

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.

Reactjs: Is it necessary to copy object in component state before modifying it?

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)
})

Categories