使用嵌套数组中的值对json对象进行分组和转换

时间:2017-07-11 21:29:25

标签: javascript json node.js lodash

我正在尝试聚合并转换以下json:

[
{ 
    "orderId" : "01",
    "date" : "2017-01-02T06:00:00.000Z",
    "items" : [
        {
            "itemId": 100,
            "itemCost": 12,
            "itemQuantity": 10
        },
        {
            "itemId": 102,
            "itemCost": 25,
            "itemQuantity": 4
        }
    ]
},
{
    "orderId": "02",
    "date" : "2017-01-08T06:00:00.000Z",
    "items" : [
        {
            "itemId": 100,
            "itemCost": 15,
            "itemQuantity": 2
        },
        {
            "itemId": 101,
            "itemCost": 20,
            "itemQuantity": 5
        },
        {
            "itemId": 102,
            "itemCost": 25,
            "itemQuantity": 1
        }
    ]
},
{
    "orderId": "03",
    "date" : "2017-02-08T06:00:00.000Z",
    "items" : [
        {
            "itemId": 100,
            "itemCost": 15,
            "itemQuantity": 2
        },
        {
            "itemId": 101,
            "itemCost": 20,
            "itemQuantity": 5
        },
        {
            "itemId": 102,
            "itemCost": 25,
            "itemQuantity": 1
        }
    ]
}]

到按itemId分组的对象,然后按数量汇总,并按月总计成本(项目成本*每个订单的项目数量)汇总。例如:

[
    {
        "itemId": 100,
        "period": [
            {
                "month": "01/17",
                "quantity": 12,
                "cost": 130
            }
        ]
    },
    {
        "itemId": 101,
        "period": [
            {
                "month": "01/17",
                "quantity": 5,
                "cost": 100
            },
            {
                "month": "02/17",
                "quantity": 5,
                "cost": 100
            }
        ]
    },
    {
        "itemId": 102,
        "period": [
            {
                "month": "01/17",
                "quantity": 5,
                "cost": 125
            },
            {
                "month": "02/17",
                "quantity": 1,
                "cost": 25
            }
        ]
    }
]

我的桌子上有一个小的凹痕,我一直在试图用本地地图/ reduce或lodash来计算如何做到这一点。

3 个答案:

答案 0 :(得分:3)

你可以这样做:



var orders = [{orderId:"01",date:"2017-01-02T06:00:00.000Z",items:[{itemId:100,itemCost:12,itemQuantity:10},{itemId:102,itemCost:25,itemQuantity:4}]},{orderId:"02",date:"2017-01-08T06:00:00.000Z",items:[{itemId:100,itemCost:15,itemQuantity:2},{itemId:101,itemCost:20,itemQuantity:5},{itemId:102,itemCost:25,itemQuantity:1}]},{orderId:"03",date:"2017-02-08T06:00:00.000Z",items:[{itemId:100,itemCost:15,itemQuantity:2},{itemId:101,itemCost:20,itemQuantity:5},{itemId:102,itemCost:25,itemQuantity:1}]}];

// First, map your orders by items
var items = {};
orders.forEach(function(order) {

    // set the month of each order
    var month = new Date(order.date);
    month = ('0' + (month.getMonth() + 1)).slice(-2) + '/' +  String(month.getFullYear()).slice(-2);
    
    // for each item in this order
    order.items.forEach(function(item) {
    
        // here we already have both keys: "id" and "month"
        // then, we make sure they have an object to match
        var id = item.itemId;
        if (!items[id]) {
            items[id] = {};
        }
        if (!items[id][month]) {
            items[id][month] = { cost:0, quantity:0 };
        }
        
        // keep calculating the total cost
        items[id][month].cost += item.itemCost * item.itemQuantity;
        items[id][month].quantity += item.itemQuantity;
    });
});

// Now, we format the calculated values to your required output:
var result = Object.keys(items).map(function(id) {
    var obj = {
        itemId: id,
        period: Object.keys(items[id]).map(function(month) {
            items[id][month].month = month;
            return items[id][month];
        }),
    };
    return obj;
});

console.log(result);




希望它有所帮助。

答案 1 :(得分:2)

您可以使用此转换:

const result = Object.values(myList.reduce( (acc, o) => {
    const month = o.date.substr(5,2) + '/' + o.date.substr(2,2);
    return o.items.reduce ( (acc, item) => {
        const it = acc[item.itemId] || {
                itemId: item.itemId,
                period: {}
            }, 
            m = it.period[month] || {
                month: month,
                quantity: 0,
                cost: 0
            };
        m.cost += item.itemCost * item.itemQuantity;
        m.quantity += item.itemQuantity;
        it.period[month] = m;
        acc[item.itemId] = it;
        return acc;
    }, acc);
}, {})).map( o => 
    Object.assign({}, o, { period: Object.values(o.period) }) 
);

const myList = [
{ 
    "orderId" : "01",
    "date" : "2017-01-02T06:00:00.000Z",
    "items" : [
        {
            "itemId": 100,
            "itemCost": 12,
            "itemQuantity": 10
        },
        {
            "itemId": 102,
            "itemCost": 25,
            "itemQuantity": 4
        }
    ]
},
{
    "orderId": "02",
    "date" : "2017-01-08T06:00:00.000Z",
    "items" : [
        {
            "itemId": 100,
            "itemCost": 15,
            "itemQuantity": 2
        },
        {
            "itemId": 101,
            "itemCost": 20,
            "itemQuantity": 5
        },
        {
            "itemId": 102,
            "itemCost": 25,
            "itemQuantity": 1
        }
    ]
},
{
    "orderId": "03",
    "date" : "2017-02-08T06:00:00.000Z",
    "items" : [
        {
            "itemId": 100,
            "itemCost": 15,
            "itemQuantity": 2
        },
        {
            "itemId": 101,
            "itemCost": 20,
            "itemQuantity": 5
        },
        {
            "itemId": 102,
            "itemCost": 25,
            "itemQuantity": 1
        }
    ]
}];

const result = Object.values(myList.reduce( (acc, o) => {
    const month = o.date.substr(5,2) + '/' + o.date.substr(2,2);
    return o.items.reduce ( (acc, item) => {
        const it = acc[item.itemId] || {
                itemId: item.itemId,
                period: {}
            }, 
            m = it.period[month] || {
                month: month,
                quantity: 0,
                cost: 0
            };
        m.cost += item.itemCost * item.itemQuantity;
        m.quantity += item.itemQuantity;
        it.period[month] = m;
        acc[item.itemId] = it;
        return acc;
    }, acc);
}, {})).map( o => 
    Object.assign({}, o, { period: Object.values(o.period) }) 
);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 2 :(得分:2)

我认为那里的其他答案从香草的角度来看做得非常好,所以我想采取更多的lodash密集的方法,因为你提到它作为标签。这主要是一个有趣的挑战,但我希望解决方案足够优雅,可以从中提升组件。

在我们开始之前,我将使用vanilla lodash模块和lodash的functional programming flavor。让fp成为函数式编程模块,_为vanilla(并让orders成为您的原始数据结构)。此外,作为一项挑战,我将尽最大努力减少vanilla JS方法和箭头函数,以最大化lodash方法和函数创建方法。

首先,让我们连续获取所有商品,并与订单信息配对:

const items = _.flatMap(orders, o=> _.map(o.items, i=> [i, o]));

我知道我说我想最小化箭头功能,但是我无法想到任何其他方式将订单对象放到链的末尾。挑战自己,根据作文重写上述内容(例如fp.compose_.flow),看看会发生什么。

我现在说,通过商品ID对我们的对进行分组是个好时机:

const id_to_orders = _.groupBy(items, fp.get('[0].itemId'));

这里,fp.get('[0].itemId')为我们提供了一个函数,给定一个数组,返回第一个元素的itemId(在我们的例子中,我们有一个对列表,其中第一个元素是item,第二个是相关的订单对象)。因此,id_to_orders是从商品ID到所有订购时间列表的地图。

id_to_orders地图看起来非常接近我们之后的数据结构。从高层次来看,剩下的就是将每个项目的订单数据转换为按月分组的数量和成本。

const result = _.mapValues(id_map, fp.flow(
    // Arrange the item's orders into groups by month
    fp.groupBy(month)

    // We're done with the order objects, so fp.get('[0]') filters them
    // out, and the second function pairs the item's cost and quantity
  , fp.mapValues(fp.flow(
        fp.map(fp.flow(fp.get('[0]'), i=> [i.itemCost, i.itemQuantity]))

        // Sum up the cost (left) and quantity (right) for the item for the month
      , fp.reduce(add_pair, [0, 0])))

    // These last couple lines just transform the resulting data to look
    // closer to the desired structure.
  , _.toPairs
  , fp.map(([month, [cost, count]])=> ({month, cost, count}))
));

上面提到的帮助者monthadd_pair

function month([item, order]){
  const date  = new Date(order.date)
      , month = date.getMonth() + 1
      , year  = date.getFullYear().toString().slice(-2);
  return `${month}/${year}`;
}

function add_pair(p1, p2){
  return [p1[0] + p2[0], p1[1] + p2[1]];
}

出于好奇(或虐待狂),让我们看看这整个事物看起来像链接在一起作为一条管道:

const get_order_data = fp.flow(
    fp.flatMap(o=> _.map(o.items, i=> [i, o]))
  , fp.groupBy(fp.get('[0].itemId'))
  , fp.mapValues(fp.flow(
        fp.groupBy(month)
      , fp.mapValues(fp.flow(
            fp.map(fp.flow(fp.get('[0]'), i=> [i.itemCost, i.itemQuantity]))
          , fp.reduce(add_pair, [0, 0])))
      , _.toPairs
      , fp.map(([month, [cost, count]])=> ({month, cost, count})))
));

const result = get_order_data(orders);

您会注意到这个撰写的版本有更多fp(而不是_)。如果您对这种方式更加容易感到好奇,我建议您阅读lodash FP guide

jsfiddle包含所有内容。

最后,如果您想将完全上面的代码中的结果转换为您在帖子中提到的输出格式,请按以下步骤推荐:

const formatted = _.keys(result).map(k=> ({itemId: k, periods: result[k]}));