Reputation: 7601
I have an existing array of objects
const oldData = [
{'one': 1, 'two': 2},
{'one': 3, 'two': 4}
];
I have a new array of objects:
const newData = [
{'three': 5, 'two': 6, 'one': 7},
{'five': 8, 'one': 9, 'two': 10},
];
I have an array containing object props I want to extract (this is a variable)
const columnMeta = ['one', 'two'];
I'd like to create a composable, reusable function using Ramda. This function should extract only the chosen columnMeta properties of newArray and append that with whatever that existed in oldArray
The following works but seems a tad bit too verbose (and hardly reusable..)
// simple enough, join two arrays using the spread operator
const appendData = curry((curr, prev) => [...prev, ...curr]);
// get a subset of an object, given an array of keys
const extractByColumnMeta = curry((k, obj) => zipObj(k, props(k)(obj)));
// create a composable function that takes an array of keys
const mapDataByColumns = compose(map, extractByColumnMeta)(columnMeta);
// compose a new function that would take a the newData & oldData
const mergeDataAfterMap = compose(appendData, mapDataByColumns);
// works, but I'm sure I can do better :)
mergeDataAfterMap(newData)(oldData);
// => [{one: 1, two: 2}, {one: 3, two: 4}, {one: 7, two: 6}, {one: 9, two: 10}]
Non ramda way
const oldData = [
{'one': 1, 'two': 2},
{'one': 3, 'two': 4}
];
const newData = [
{'three': 5, 'two': 6, 'one': 7},
{'five': 8, 'one': 9, 'two': 10},
];
const columnMeta = ['one', 'two'];
const mapDataToColumns = (fields, oldData, newData) => {
// a new array. anything in oldData is overwritten
return [...oldData, ...newData.map(row => {
// reduce to return only wanted keys
return fields.reduce((acc, field) => {
// add to accumulator or return
return row.hasOwnProperty(field)
? {...acc, [field]:row[field]}
: acc;
}, {});
})];
}
console.log(mapDataToColumns(columnMeta, oldData, newData));
Upvotes: 0
Views: 734
Reputation: 7601
Building upon @ScottSauyet's answer and comments to @naomik's answer, I came up with this:
const pickAndMerge = compose(chain, project);
And then it's simply,
const oldData = [
{'one': 1, 'two': 2},
{'one': 3, 'two': 4}
];
const newData = [
{'three': 5, 'two': 6, 'one': 7},
{'five': 8, 'one': 9, 'two': 10},
];
const columnMeta = ['one', 'two'];
const pickAndMerge = R.compose(R.chain, R.project);
console.log(pickAndMerge(columnMeta)([oldData, newData]));
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.20.0/ramda.min.js"></script>
Upvotes: 1
Reputation: 727
How about:
const mergeSelectAttr = R.curry((oldData, columnMeta, newData) =>
R.compose(
R.concat(oldData),
R.map(R.pick(columnMeta))
)(newData)
)
I would say this yields to a more flexible api than the current answers, you may call it as per your convenience:
mergeSelectAttr(oldData, columnMeta, newData)
mergeSelectAttr(oldData)(columnMeta, newData)
mergeSelectAttr(oldData, columnMeta)(newData)
One major advantage you get it with this solution would work even if you get your newData
at a later stage, say from an api call / DB result, it would still work great due to currying. You can of course also change the order of oldData
and columnMeta
depending on what you are getting first
Eg:
// You have olData and coulmnMeta now, but not newData:
const merger = mergeSelectAttr(oldData, columnMeta)
.
.
.
.
// A little while later, when you have access to newData:
const finalValue = merger(newData)
Upvotes: 2
Reputation: 135416
You just need to compose R.pick
with R.concat
, but in a somewhat tricky way. You're trying to compose a unary function, R.pick(columnMenta)
, with a binary function, R.concat
.
const compose2 = R.compose(R.compose, R.compose)
const foo = compose2(R.map(R.pick(['one', 'two'])), R.concat)
console.log(foo(oldData, newData))
// => [{"one":1,"two":2},{"one":3,"two":4},{"one":7,"two":6}, {"one":9,"two":10}]
The arbitrary limitation of two inputs makes this a little weak tho, and most people don't like compose2
trickery. What if you had several lists of input data and you wanted to combine them in a sensible way? Enter R.chain
…
const foo = R.chain(R.map(R.pick(['one', 'two'])))
console.log(foo([oldData, newData, oldData]))
// => [{"one":1,"two":2},{"one":3,"two":4},{"one":7,"two":6},{"one":9,"two":10},{"one":1,"two":2},{"one":3,"two":4}]
Note foo
now expects an array of inputs which effectively allows you to combine as many data sets as you need
OK, it seems you also want the columnMeta
to be an input to the function. It will be easiest if we adapt the latter answer
const foo = R.curry((columns, xxs) =>
R.chain(R.map(R.pick(columns)), xxs))
console.log(foo(['one'], [oldData, newData, oldData]))
// => [{"one":1},{"one":3},{"one":7},{"one":9},{"one":1},{"one":3}]
console.log(foo(['two'], [oldData, newData, oldData]))
// => [{"two":2},{"two":4},{"two":6},{"two":10},{"two":2},{"two":4}]
console.log(foo(['one', 'two'], [oldData, newData, oldData]))
// => [{"one":1,"two":2},{"one":3,"two":4},{"one":7,"two":6},{"one":9,"two":10},{"one":1,"two":2},{"one":3,"two":4}]
Upvotes: 1
Reputation: 50807
If you don't mind this API:
mergeAndPick(columnMeta, [oldData, newData]);
//=> [{"one": 1, "two": 2}, {"one": 3, "two": 4},
// {"one": 7, "two": 6}, {"one": 9, "two": 10}]
Then you can write it simply like this:
const mergeAndPick = R.useWith(R.project, [R.identity, R.unnest]);
If you really want this:
mergeDataAfterMap(columnMeta, oldData, newData);
//=> [{"one": 1, "two": 2}, {"one": 3, "two": 4},
// {"one": 7, "two": 6}, {"one": 9, "two": 10}]
Then you could build it on top of the above.
const mergeDataAfterMap = (columnMeta, oldData, newData) =>
mergeAndPick(columnMeta, [oldData, newData]);
But the first one is more flexible, allowing you to specify any number of lists to combine.
This is built on top of a fairly unusual Ramda function, useWith
, which makes it easier to combine functions in a points-free manner. In the days of ES6, it's less of an obvious advantage, and that could be rewritten as
const mergeAndPick = R.curry((columnMeta, lists) =>
R.project(columnMeta, R.unnest(lists)));
You can see all this in action on the Ramda REPL.
Upvotes: 1