6

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
11
  • Sounds like a very specific case here... Is the ruleset always the same? also, is it meant to be recursive? Commented Aug 23, 2018 at 15:29
  • No it's only one level ... and yes, the ruleset is always the same Commented Aug 23, 2018 at 15:30
  • So what would be the desired result with [{ "key1": "value1", "key2": "value2", "key3": ["value3", "value4"], "key4":["value5","value6","value7"] }] ? Commented Aug 23, 2018 at 15:32
  • As I said ... the ruleset is always the same. Which means, I only want to expand the key3 property which is always an array. The other properties should not be touched but just copied to the new elements Commented Aug 23, 2018 at 15:39
  • 3
    @devnull69 just wondering are downvoting the answers? If so can you elaborate how they do not fit as solutions for you please? A comment would be great. Commented Aug 23, 2018 at 19:25

3 Answers 3

1
+100

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

Sign up to request clarification or add additional context in comments.

2 Comments

Wow, you just rule. The result is perfect ... a little bit more complicated than I can handle :-) You deserve the bounty
@devnull69 Thanks :) It was an interesting challenge, that was also quite maddening. I didn't realize how much I depend on lambda functions to transform objects XD. I'm glad this will work for you!
1

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)

Comments

0

Here's a solution using vanilla JS

let array = [{
    "key1": "value1",
    "key2": "value2",
    "key3": ["value3", "value4"]
}, {
    "key1": "value5",
    "key2": "value6",
    "key3": ["value7", "value8"]
}, ]

const result = array.reduce((final, item) => {
    for (let i = 0; i < item.key3.length; i++) {
        final.push(Object.assign({}, item, {
            "key3": item.key3[i]
        }))
    }
    return final;
}, []);

console.log(result);

If you were to use lodash, I think the equivalent will be:

const _ = require('lodash')
const result = _.reduce(array, (final, item) => {
    _.forEach(item.key3, (key3Val) => {
        final.push(Object.assign({}, item, {
            "key3": key3Val
        }))
    })
    return final;
}, []);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.