0

I have some objects in the shape of below.

[{
    product: 'ABC',
    productId: 'AB123',
    batch: 'BA1',
    price: '12'
}, {
    product: 'ABC',
    productId: 'AB123',
    batch: 'BA2',
    price: '15'
}, {
    product: 'XYZ',
    productId: 'AB124',
    batch: 'XY1',
    price: '124'
}]

I want to merge objects into one single object in the array if key pair (product, and productId) are mathced, in the below format.

[{
    product: 'ABC',
    productId: 'AB123',
    batch: ['BA1', 'BA2'],
    price: ['12', '15']
}, {
    product: 'XYZ',
    productId: 'AB124',
    batch: 'XY1',
    price: '124'
}]

How can I do it in lodash or in pure javascript.

2
  • 2
    Sharing your research helps everyone. Tell us what you've tried and why it didn't meet your needs. This demonstrates that you've taken the time to try to help yourself, it saves us from reiterating obvious answers, and most of all it helps you get a more specific and relevant answer! Also see how to ask Commented Nov 17, 2016 at 14:04
  • The expected resulting object contains different structures. If there is multiple entries for a product you have an array for batch and price but if there isn't then you expect only a single value. - Shouldn't the resulting objects follow a common format? i.e: Always arrays for batch/price or maybe even a single array containing objects with price and batch together so you know which batch belongs to which price? Commented Nov 17, 2016 at 14:16

5 Answers 5

3

This proposal does not alter the given data.

It creates new objects, first with just single data, later if more data should be grouped, it uses an array for batch and price.

var data = [{ product: 'ABC', productId: 'AB123', batch: 'BA1', price: '12' }, { product: 'ABC', productId: 'AB123', batch: 'BA2', price: '15' }, { product: 'XYZ', productId: 'AB124', batch: 'XY1', price: '124'}],
    merged = [];

data.forEach(function (a) {
    if (!this[a.productId]) {
        this[a.productId] =  { product: a.product, productId: a.productId, batch: a.batch, price: a.price };
        merged.push(this[a.productId]);
        return;
    }
    if (!Array.isArray(this[a.productId].batch)) {
        this[a.productId].batch = [this[a.productId].batch];
    }
    if (!Array.isArray(this[a.productId].price)) {
        this[a.productId].price = [this[a.productId].price];
    }
    this[a.productId].batch.push(a.batch);
    this[a.productId].price.push(a.price);
}, Object.create(null));

console.log(merged);

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

3 Comments

No one liner from you this time? :D
@fafl, come on!
You are awesome! @NinaScholz
1

You can make use of _.uniqWith to loop over the collection and get rid of duplicates. Apart from that, uniqWith grants you access to the objects themselves so you can tamper them as you like.

In this case, when a duplicate is found, I add its batch and price to the array of the original object, getting the desired result.

var arr = [{
  product: 'ABC',
  productId: 'AB123',
  batch: 'BA1',
  price: '12'
}, {
  product: 'ABC',
  productId: 'AB123',
  batch: 'BA2',
  price: '15'
}, {
  product: 'XYZ',
  productId: 'AB124',
  batch: 'XY1',
  price: '124'
}];

function addToArray(val1, val2) {
  return _.isArray(val1) ? val1.concat(val2) : [val1].concat(val2);
}

function modifyObjs(a, b) {
  b.batch = addToArray(b.batch, a.batch);
  b.price = addToArray(b.price, a.price);
  return true;
}

function predicateAndModifier(a, b) {
  return a.product === b.product && a.productId === b.productId && modifyObjs(a, b);
}

console.log(_.uniqWith(arr, predicateAndModifier));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>

Comments

1

Lodash 4.17.2

_.reduce(data, function(result, item) {
    var added = _.find(result, {
        product: item.product,
        productId: item.productId
    });
    if (_.isObject(added)) {
        //!! better to merge with new object and add result to array again to avoid mutable
        added = _.mergeWith(added, item, function(addedVal, itemVal, key) {
            if (key === 'product' || key === 'productId') {
                return addedVal;
            }
            return _.concat(addedVal, itemVal);
        });
        return result;
    }
    return _.concat(result, item);
}, []);

Comments

1

You can merge similar objects in the array using a lodash's chain with _.transform() and _.mergeWith():

function mergeSimilar(arr, arrayProps) {
  // transform the array into a map object
  return _(arr).transform(function(result, item) { 
    
    // create a temp id that includes the product and productId
    var id = item.product + item.productId; 
    
    // merge the existing item with a new item
    result[id] = _.mergeWith(result[id] || {}, item, function(objValue, srcValue, key) { 
      
      // if a value exists, and it's one of the request keys, concat them into a new array
      if (!_.isUndefined(objValue) && _.includes(arrayProps, key)) {
        return [].concat(objValue, srcValue);
      }
    });
  }, {})
  .values() // get the values from the map object
  .value();
}

var arr = [{
  product: 'ABC',
  productId: 'AB123',
  batch: 'BA1',
  price: '12'
}, {
  product: 'ABC',
  productId: 'AB123',
  batch: 'BA2',
  price: '15'
}, {
  product: 'XYZ',
  productId: 'AB124',
  batch: 'XY1',
  price: '124'
}];

var result = mergeSimilar(arr, ['batch', 'price']);

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>

2 Comments

is it possible to do this for a multidimensional array without knowing the keynames?
Hard to say. Open a new question with sample data, and expected result.
0

Does it have to be readable?

var data = [{
    product: 'ABC',
    productId: 'AB123',
    batch: 'BA1',
    price: '12'
}, {
    product: 'ABC',
    productId: 'AB123',
    batch: 'BA2',
    price: '15'
}, {
    product: 'ABC',
    productId: 'AB113',
    batch: 'BA2',
    price: 15
}, {
    product: 'XYZ',
    productId: 'AB124',
    batch: 'XY1',
    price: '124'
}]
var unEs6 = function(x) {
    if (x instanceof Map) {
        var result = {}
        for (let [key, value] of x.entries()) {
            result[key] = unEs6(value);
        }
        return result;
    }
    else {
        return x
    }
}
JSON.stringify(unEs6(
    data
        .map(
            row => (new Map().set(
                row.product, new Map().set(
                    row.productId, new Map()
                        .set("batch", [row.batch])
                        .set("price", [row.price])
                    )
                )
            )
        )
        .reduce((a, b) => !a.has(b.keys().next().value) ?
            new Map([...a, ...b]) :
            !a.get(b.keys().next().value).has(b.get(b.keys().next().value).keys().next().value) ?
                a.set(b.keys().next().value, new Map([
                    ...a.get(b.keys().next().value),
                    ...b.get(b.keys().next().value)
                ])) :
                a.set(b.keys().next().value, a.get(b.keys().next().value).set(
                    b.get(b.keys().next().value).keys().next().value,
                    new Map()
                        .set("batch", a.get(b.keys().next().value).get(b.get(b.keys().next().value).keys().next().value).get("batch")
                              .concat(b.get(b.keys().next().value).get(b.get(b.keys().next().value).keys().next().value).get("batch"))
                        )
                        .set("price", a.get(b.keys().next().value).get(b.get(b.keys().next().value).keys().next().value).get("price")
                              .concat(b.get(b.keys().next().value).get(b.get(b.keys().next().value).keys().next().value).get("price"))
                        )
                ))
        )
))

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.