Search code examples
javascriptlodash

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

Solution

  • 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]');