Akhil P
Akhil P

Reputation: 1620

Javascript merge two objects based on key

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.

Upvotes: 0

Views: 2786

Answers (5)

Ori Drori
Ori Drori

Reputation: 191976

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>

Upvotes: 1

stasovlas
stasovlas

Reputation: 7416

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);
}, []);

Upvotes: 1

acontell
acontell

Reputation: 6922

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>

Upvotes: 1

fafl
fafl

Reputation: 7385

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"))
                        )
                ))
        )
))

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386620

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

Upvotes: 3

Related Questions