Reputation: 596
I have an array of objects which I need to parse in some way. The objects have a set of common fields and some optional fields that aren't present in all objects. For the sake of the example, a,b,c
are common fields and d,e,f
are optional. I want to perform some action on some of the fields, e.g double the value of a
and capitalize d
, while leaving the rest as they were. If an object was missing one or more of the optional fields, it should remain so in the result.
The catch is that I want to do it in a purely functional way, without declaring an empty array and pushing into it.
Example input:
const input = [
{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
Expected result:
[
{
a: 6,
b: 'test',
c: 34,
d: 'EXAMPLE'
},
{
a: 12,
b: 'another',
c: 0,
e: true,
f: () => {}
}
]
What I tried so far was using map
like so:
const result = input.map(x => ({
a: 2 * x.a,
d: x.d.toUppercase()
});
Or like so:
const result = input.map(x => ({
a: 2 * x.a,
b,
c,
d: x.d.toUppercase(),
e,
f
});
But this results in objects that either contain only the fields which were manipulated, or all of them, regardless if they existed in the original object.
Any suggestions on how to accomplish that?
Upvotes: 1
Views: 1593
Reputation: 23372
It might be worth googling for lenses. Many "functional" javascript libraries implement them to easily chain the kinds of data modifications you describe.
Under the hood, they might use the same Object.assign
approach you've already found, but they allow for readable, reusable and easily chainable code.
A quick & dirty implementation to show the change in syntax (es6):
const propLens = prop => ({
get: obj => obj[prop],
// Return a new object with the property set to
// a value
set: (val, obj) => Object.assign({}, obj, {
[prop]: val
}),
// Return a new object with the property mapped
// by a function
over: (fn, obj) => Object.assign({}, obj, {
[prop]: fn(obj[prop])
})
});
// Helpers to create specific set/over functions
const set = (lens, val) => obj => lens.set(val, obj);
const over = (lens, fn) => obj => lens.over(fn, obj);
// Some utils for the example
const { pipe, double, toUpper } = utils();
// Now, you can define your modification by a set
// of lens + set/over combinations
const convertData = pipe(
set( propLens("d"), "added" ),
over( propLens("a"), double ),
over( propLens("b"), toUpper )
);
// Example data
const item = {a: 5, b: "hello", c: "untouched"};
// Converted copy
const convertedItem = convertData(item);
console.log("Converted:", convertedItem);
console.log("item !== convertedItem ->", item !== convertedItem);
// Utils
function utils() {
return {
pipe: (...fns) => x =>
fns.reduce((acc, f) => f(acc), x),
double: x => x * 2,
toUpper: x => x.toUpperCase()
};
}
(note that this example will run a bit slower than doing everything in one function, but you'll probably only notice for large amounts of data)
If you like the lenses-approach, I'd advice to use a well maintained and tested library (like Ramda). You'll even get more awesome features like index lenses and lens paths, and things are curried by default.
Upvotes: 0
Reputation: 33466
If you want your objects to be immutable, you will need to make a copy of the objects before making the needed changes. Object.assign
allows you to do this in one expression:
const result = input.map(x => Object.assign({}, x,
{ a: 2 * x.a },
('d' in x) && { d: x.d.toUpperCase() }
));
const input = [{
a: 3,
b: 'test',
c: 34,
d: 'example'
}, {
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
const result = input.map(x => Object.assign({},
x,
{ a: 2 * x.a },
('d' in x) && { d: x.d.toUpperCase() }
));
console.log(result);
.as-console-wrapper { min-height: 100%; }
Upvotes: 2
Reputation: 191946
This solution requires Babel's Object rest spread transform plugin because Object Rest/Spread Properties for ECMAScript is still in the proposals stage.
Create a new object. Spread the original object into the new one. If the properties you wish to change exist in the original object, return an object with the the new property value to the spread. If not, return null
(spread ignores undefined
and null
). The new values will override the old.
const input = [{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
const result = input.map(x => ({
...x, // spread all props of x
...('a' in x ? { a: 2 * x.a } : null), // override a if exists
...('d' in x ? { d: x.d.toUpperCase() } : null) // override d if exists
}));
console.log(result);
Upvotes: 1
Reputation: 2753
Inside the map function, you can first add the conditions to manipulate the input and then you need to return the modified object.
const input = [
{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
let output = input.map(o => {
if(o.a) { o.a = o.a * 2; }
if(o.d) { o.d = o.d.toUpperCase(); }
return o;
});
console.log(output)
Output::
[
{
"a": 6,
"b": "test",
"c": 34,
"d": "EXAMPLE"
},
{
"a": 12,
"b": "another",
"c": 0,
"e": true,
"f": () => {}
}
]
Upvotes: 0
Reputation: 50291
map
function be still used.In this case you can check if a key a
exist in the object then do the necessary operation only on it's value.
Hope this snippet will be useful
const input = [{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
input.map(function(elem, index) {
if (elem.hasOwnProperty('a')) {
elem.a = elem['a'] * 2;
}
})
console.log(input)
Upvotes: 0
Reputation: 53958
You could use the map
method and make a check in each object if the key, whose value you want to change, exists. If so, then yuo can alter correspondingly the value of the key.
const input = [
{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
const result = input.map(function(obj){
if(obj.a){
obj.a = 2 * obj.a;
}
if(obj.d){
obj.d = obj.d.toUpperCase()
}
return obj;
});
console.log(result);
Upvotes: 0