0
[{
    "_id": {
        "year": 2017,
        "month": 4
    },
    "Confirm": 0
}, {
    "_id": {
        "year": 2017,
        "month": 4
    },
    "Expired": 25
}, {
    "_id": {
        "year": 2017,
        "month": 4
    },
    "Pending": 390
}, {
    "_id": {
        "year": 2017,
        "month": 5
    },
    "Pending": 1400
}]

The array above contain same value month and year. Generated from MongoDB Aggregate. And I want to merge them into a single object and preserve whatever keys and values they have.

Expected output:

[{
    month: 4,
    year: 2017,
    Expired: 25,
    Pending: 390
}, {
    month: 5,
    year: 2017,
    Pending: 1400
}]

I prefer the fastest execution implementation. Underscorejs or native are welcome. Thanks

Daniel Pérez
  • 1,873
  • 1
  • 17
  • 25
Muhaimin
  • 1,643
  • 2
  • 24
  • 48

3 Answers3

1

This takes a little to pick apart, but it is linear:

const ary = [{
    "_id": {
        "year": 2017,
        "month": 4
    },
    "Confirm": 0
}, {
    "_id": {
        "year": 2017,
        "month": 4
    },
    "Expired": 25
}, {
    "_id": {
        "year": 2017,
        "month": 4
    },
    "Pending": 390
}, {
    "_id": {
        "year": 2017,
        "month": 5
    },
    "Pending": 1400
}];

const result = Object.values(ary.reduce((acc, cur) => {
    const { month, year } = cur._id;
    const key = `${month}-${year}`;
    const obj = Object.assign({}, cur);
    delete obj._id;
    acc[key] = Object.assign(acc[key] || { month, year }, obj);
    return acc;
}, {}));

console.log(result);
Andy Gaskell
  • 31,495
  • 6
  • 74
  • 83
0

This runs in O(N*logN) for sorting and O(N) for merging the json. Hope this works for you!

var obj = [{
    _id: {
        year: 2017,
        month: 5,
    },
    Pending: 1400,
}, {
    _id: {
        year: 2017,
        month: 4,
    },
    Expired: 25,
}, {
    _id: {
        year: 2017,
        month: 4,
    },
    Pending: 390,
}, {
    _id: {
        year: 2017,
        month: 4,
    },
    Confirm: 0,
}];

function compare(a, b) {
    return a._id.year !== b._id.year
        ? a._id.year - b._id.year
        : a._id.month - b._id.month;
}

var sorted = obj.sort(compare);

function join(a, b) {
    return {
        _id: a._id,
        Pending: (a.Pending? a.Pending : 0) + (b.Pending? b.Pending : 0),
        Confirm: (a.Confirm? a.Confirm : 0) + (b.Confirm? b.Confirm : 0),
        Expired: (a.Expired? a.Expired : 0) + (b.Expired? b.Expired : 0),
    };
}

var compressed = sorted.filter(function (value, index) {
    if (!sorted[index + 1]) {
        return true;
    }
    if (compare(value, sorted[index + 1]) === 0) {
        sorted[index + 1] = join(value, sorted[index + 1]);
        return false;
    }
    return true;
});

console.log(compressed);

// if you want month and year formatted:

console.log(compressed.map(function (o) {
    const result = {
        month: o._id.month,
        year: o._id.year,
    };
    if (o.Pending !== undefined) result.Pending = o.Pending;
    if (o.Confirm !== undefined) result.Confirm = o.Confirm;
    if (o.Expired !== undefined) result.Expired = o.Expired;
    return result;
}));
Daniel Pérez
  • 1,873
  • 1
  • 17
  • 25
0

You could use a Map for grouping, and then Array.from to extract the final objects:

function merge(data) {
    return Array.from(data.reduce( (acc, o) => {
        const k = o._id.year * 100 + o._id.month;
        const v = acc.get(k) || Object.assign({}, o._id);
        for (let prop in o) {
            if (prop !== '_id') v[prop] = o[prop];
        }            
        return acc.set(k, v);
    }, new Map), ([k, v]) => v);
}
// Sample data
const data = [{
    "_id": {
        "year": 2017,
        "month": 4
    },
    "Confirm": 0
}, {
    "_id": {
        "year": 2017,
        "month": 4
    },
    "Expired": 25
}, {
    "_id": {
        "year": 2017,
        "month": 4
    },
    "Pending": 390
}, {
    "_id": {
        "year": 2017,
        "month": 5
    },
    "Pending": 1400
}];

const result = merge(data);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
trincot
  • 317,000
  • 35
  • 244
  • 286