【问题标题】:Group sum and transform json object with values in nested array使用嵌套数组中的值对总和和转换 json 对象进行分组
【发布时间】:2017-07-11 21:29:25
【问题描述】:

我正在尝试聚合和转换以下 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
            }
        ]
    }
]

我的办公桌上有一个小凹痕,我一直在努力思考如何使用本机 map/reduce 或 lodash 来做到这一点。

【问题讨论】:

    标签: javascript json node.js lodash


    【解决方案1】:

    你可以这样做:

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

    希望对你有帮助。

    【讨论】:

      【解决方案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; }

      【讨论】:

      • 这太完美了。我必须使用 Object.keys 转换 Object.values,因为我使用的节点版本不支持这种极其强大和方便的方法。我现在可能最终会使用 polyfill。
      【解决方案3】:

      我认为从普通的角度来看,其他答案做得很好,所以我想尝试一种更密集的方法,因为你提到它是一个标签。这主要只是一个有趣的挑战,但我希望解决方案足够优雅,让您可以从中提取组件。

      在开始之前,我将同时使用 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(在我们的例子中,我们有一个对列表,其中第一个元素是项目,第二个其中是相关的订单对象)。因此,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]}));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-26
        • 1970-01-01
        • 1970-01-01
        • 2018-02-25
        相关资源
        最近更新 更多