2

I'm going to pre-face this with saying i'm not sure this is the best approach so other approaches are greatly appreciated

End Goal: To store a list of products and the toppings purchased by calling the woocommerce API and using the response data

I'm calling the woocommerce REST api that provides me a good chunk of JSON data back. In the JSON are line_items. These are the products purchased. Nested in line_items are meta_data, this is the toppings for example tomato or sauce.

Attached an image of the JSONenter image description here

So what i'm trying to do is create something like this

var testOrderItems =
  [{
      title: "Fried Chicken Burger",
      meta: [
        "Lettuce",
        "cheese slice",
        "kethcup"
      ]
    },
    {
      title: "Beef Burger",
      meta: [
        "Lettuce",
        "cheese slice",
        "kethcup"
      ]
    }
  ]

which will follow my schema for oder items

var orderItems = new Schema({
    title: {type: String, required: true},
    meta: [{type: String}]

});

So to do this, i figured I would just do a forloop or foreach through the JSON to get all the product names and their meta. Getting actual values is easy. The hard part is creating the array or JSON object that I can then store, i'm just not sure how to create it whilst in the loop. Below are a few things I tried

let fullData = JSON.parse(result)

//parsed response from woocommerce API call

fullData.line_items.forEach((product, index) => {
  //for each line item get me the product
  orderItems.push(product.name)
  //var namey = 
  //push the product name to the orderItemsArray
  product.meta_data.forEach(function(meta) {
    //checks for string as one of the plug-ins fills the meta with more nested information and we only want the top level string
    if (typeof meta.value === 'string' || meta.value instanceof String)
      // it's a string
      orderItems.push(meta.value)
    //Onbviously won't nest the meta with the product name just on new lines
  })
});

The I thought I could do it in for loops by storing an ID ref as "i" and being able to re-reference this later in the nested loop to add the meta, i got a little lost with this

var length = fullData.line_items.length

for (let i = 0; i < length; i++) {
  // console.log(i);
  console.log(fullData.line_items[i].name)
  for (let j = 0; j < fullData.line_items[i].meta_data.length; j++) {
    var metaValue = fullData.line_items[i].meta_data[j].value
    if (typeof metaValue === 'string' || metaValue instanceof String) {
     console.log(fullData.line_items[i].meta_data[j].value);
      stringMeta = fullData.line_items[i].meta_data[j].value
      //this works but has drawbacks
      //1 obviously just overwrites itself each time
      //2 will stop at the end of meta so won't add items without meta
      finalOrderItems = {
        id: i,
        name: fullData.line_items[i].name,
        meta: [stringMeta]
      }
    }
  }
}

and thats where I am, feels like this should be incredibly easy but can't quite grasp it at the moment.

Barmar
  • 741,623
  • 53
  • 500
  • 612
user2389087
  • 1,692
  • 3
  • 17
  • 39
  • Could the values of the meta object not be strings? Why do you need the test `typeof metaValue === 'string' || metaValue instanceof String`? – ibrahim mahrir Aug 17 '18 at 15:39
  • 1
    Instead of posting an image of a json, you should post an actual JSON, so people can use it to try out and test their answers – Rainer Plumer Aug 17 '18 at 15:39
  • Are you sure you want to put all the meta values in a single array? Shouldn't they be organized by the meta keys, e.g. `Toppings: ["Lettuce", "cheese slice", "ketchup"]` – Barmar Aug 17 '18 at 15:42
  • Good point about organising by meta keys. Also in regards to the comment about JSON, apologies the file was huge so didn't want to add it in and didn't see anywhere I could attach a JSON file. – user2389087 Aug 17 '18 at 15:45
  • @user2389087 So do we go Barmar's way? Organizing the metas by key? – ibrahim mahrir Aug 17 '18 at 15:47
  • @ibrahimmahrir organising the metas by key will be the next improvement, as a base of answering the question and getting it working Ja answer below works really well – user2389087 Aug 17 '18 at 15:52

2 Answers2

1

You could simply create the object that represents your schema first, then return it from a map of your json Object. So, it would look like the following:

let testOrderItems = fullData.line_items.map((product)=>{
     let obj = { name: product.name };
     obj.meta = product.meta_data.map((meta)=>{
         if (typeof meta.value === 'string' || meta.value instanceof String)
          return meta.value;
     }).filter((value)=>!!value);
    return obj;
})
console.log(testOrderItems);

Although, the if statement seems a little redundant, since the woocommerce api will simply either have meta or not. However, you may have some plugin or something which is adding more information to the meta area so i've kept it in my example.

Ja Superior
  • 459
  • 4
  • 17
  • 1
    This works great, slight amend for me I had to use "fullData.line_items.map". Also yeah there is an add-ons plug-in that adds a ton of redundant information to the meta area. Thanks for taking me to school :) – user2389087 Aug 17 '18 at 15:51
  • Awesome! so Glad I could help. I'll edit my post to reflect that change. ;-) Cheers! – Ja Superior Aug 17 '18 at 15:52
  • Hey @JaSuperior, would you mind taking a look at my question here: https://stackoverflow.com/q/75865618/1113598 – batman Mar 31 '23 at 11:09
0

This looks like a job for map and reduce not forEach. map will map each object of line_items into a new object and reduce will group and organize the metas by key for each object:

var orderItems = fullData.line_items.map(function(product) {      // map each product in line_items
    return {                                                      // into a new object
        title: product.name,                                      // with title equals to the current product's name
        meta: product.meta_data.reduce(function(acc, meta) {      // and metas accumulated from each meta object in the current product's meta_data array
            acc[meta.key] = acc[meta.key] || [];                  // first, check if there is an array for the current meta's key in the group object 'acc', if not create one
            acc[meta.key].push(meta.value);                       // add the current meta's value to that array
            return acc;
        }, {})
    }
});

Shorter using arrow functions:

var orderItems = fullData.line_items.map(product => ({
    title: product.name,
    meta: product.meta_data.reduce((acc, meta) => {
        acc[meta.key] = acc[meta.key] || [];
        acc[meta.key].push(meta.value);
        return acc;
   }, {})
}));
ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73