Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "expand" sub array in array of objects

This is my array of objects

[{
    "key1": "value1",
    "key2": "value2",
    "key3": ["value3", "value4"]
}]

The result should be like this

[{
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
}, {
    "key1": "value1",
    "key2": "value2",
    "key3": "value4"
}]

So I want to get rid of the sub array in property key3 and get the new equivalent structure, copying all the other properties.

For reasons I cannot change I am supposed to use lodash, but only in version 2.4.2

EDIT: To be more elaborate: I am using a JSON based form engine which allows to use existing functions (like lodash functions) but doesn't allow to define new functions. I also cannot use control structures like for loops. Essentially I can only use chained basic function calls including lodash.

I tried to use map, but map cannot extend an array, it can only convert one array element into something different

Is there any lodash magic I can use here?

EDIT2: Here is an example about what I mean when I say "I cannot introduce new functions". It will check if an array of objects is unique regarding a certain subset of properties

model = [{
    "key1": "value1",
    "key2": "value2",
    "key3": "valuex"
},{
    "key1": "value1",
    "key2": "value2",
    "key3": "valuey"
}]

// will give false because the two objects are not unique regarding the combination of "key1" and "key2"
_.uniq(model.map(_.partialRight(_.pick, ["key1", "key2"])).map(JSON.stringify)).length === model.length
like image 433
devnull69 Avatar asked Aug 23 '18 15:08

devnull69


2 Answers

Well, this was a challenge! I have a working solution that covers all the cases I can think of, but please let me know if there is a situation I missed.


My general approach started from the end, I knew I was going to use _.zipObject to create the result objects. From there, it was just a matter of making the other properties line up with the necessary key3 values. To do so, I simply copy the property values so each value of key3 has its own copy. Next, I link them back up and create the objects. Finally, I filter out any unnecessary copies of the objects.

NOTE: This approach will not work correctly for an undefined element in key3. I considered this to be an unlikely situation, thus did not attempt to address.


The understandable version:

const objects = [{
    "key1": "value1",
    "key2": "value2",
    "key3": ["value3", "value4"]
},
{
    "key1": "value5",
    "key2": "value6",
    "key3": ["value7"]
}];

// Get other key names
const otherKeys = _.without(_.keys(objects[0]), "key3");
// Get values without key3
const otherValues = _.map(_.map(objects, _.partialRight(_.omit, "key3")), _.values);
// Get just key3 values
const onlyKey3 = _.map(objects, "key3");

// Generate dummy range of needed length
const maxLengthKey3 = _.max(_.map(onlyKey3, "length"));
const dummyRange = _.range(maxLengthKey3);

// Grow all arrays to needed length
const newOtherValues = _.flatten(_.map(dummyRange, _.partial(_.identity, otherValues)), true);
const newKey3 = _.flatten(_.map(dummyRange, _.partial(_.map, onlyKey3)));

const pairedValues = _.map(_.zip(newOtherValues, newKey3), _.flatten);
const resultObjects = _.map(pairedValues, _.partial(_.zipObject, _.union(otherKeys, ["key3"])));

// Filter out unnecessary objects
const result = _.filter(resultObjects, "key3");

All in one line:

const objects = [{
    "key1": "value1",
    "key2": "value2",
    "key3": ["value3", "value4"]
},
{
    "key1": "value5",
    "key2": "value6",
    "key3": ["value7"]
}];
// One line
const result = _.filter(_.map(_.map(_.zip(_.flatten(_.map(_.range(_.max(_.map(_.map(objects, "key3"), "length"))), _.partial(_.identity, _.map(_.map(objects, _.partialRight(_.omit, "key3")), _.values))), true), _.flatten(_.map(_.range(_.max(_.map(_.map(objects, "key3"), "length"))), _.partial(_.map, _.map(objects, "key3"))))), _.flatten), _.partial(_.zipObject, _.union(_.without(_.keys(objects[0]), "key3"), ["key3"]))), "key3");

Performance:

I expect it to be terrible for a large initial array, or for a large length key3. I especially shudder at the single line version. If anyone complains, I'd make the point that this is caused by the limitations of the execution environment.


This was tested in the browser via https://npm.runkit.com/lodash, using var _ = require('[email protected]');

like image 57
Vlad274 Avatar answered Oct 12 '22 03:10

Vlad274


let obj = {
    key1: "value1",
    key2: "value2",
    key3: ["value3", "value4"]
}

let tracker = new Array(obj.key3.length)

let newObjArr = []

for (let i = 0; i < tracker.length; i++) {
  newObjArr.push({
    key1: obj.key1,
    key2: obj.key2,
    key3: obj.key3[i]
  })
}

console.log(newObjArr)
like image 22
Francis Leigh Avatar answered Oct 12 '22 03:10

Francis Leigh