简体   繁体   中英

How to access innermost levels of doubly nested arrays/objects using plain Javascript?

I suspect the answer is simple but I've searched this site and others and haven't found one. I have a doubly nested data structure and can't figure out how to iterate over the innermost levels. I'm guessing it might involve the forEach() or map() method but nothing I've tried works.

Background: I've simplified the data for this question. The data (pasted below) are stored in an array containing 2 retail store objects. Each store object has a visits property whose value is an array of visit objects. Each visit (object) is identified by a visit date (assume there can be at most 1 visit to store A on date B). Each visit object contains a values property whose value is an array of transactions (purchases or returns) made at the visit. In the actual full data, the number of transactions per date per store is highly variable.

Tasks I need help with: (a) rename property key to visitDate , (b) rename property values to transactions , (c) delete the 8 redundant properties (from storeID to storeVisitDate ) but retain the action and dollarAmount properties, and (d) rename property dollarAmount to dollars .

Any help would be most appreciated. Thanks.

 [
   {
     "storeName": "Ye Olde Candy Shoppe",
     "address": "1313 Vampire Lane, Cityville NY 99999",
     "zipCode": "99999",
     "storeSize": "large",
     "visits": [
       {
         "key": "5/3/12",
         "values": [
           {
             "storeID": "53454447",
             "storeName": "Ye Olde Candy Shoppe",
             "city": "Cityville",
             "building": "1313",
             "street": "Vampire Lane",
             "zipcode": "99999",
             "storeSize": "large",
             "storeVisitDate": "5/3/12",
             "action": "Return",
             "dollarAmount": "65.43"
           },
           {
             "storeID": "53454447",
             "storeName": "Ye Olde Candy Shoppe",
             "city": "Cityville",
             "building": "1313",
             "street": "Vampire Lane",
             "zipcode": "99999",
             "storeSize": "large",
             "storeVisitDate": "5/3/12",
             "action": "Purchase",
             "dollarAmount": "12.43"
           },
           {
             "storeID": "53454447",
             "storeName": "Ye Olde Candy Shoppe",
             "city": "Cityville",
             "building": "1313",
             "street": "Vampire Lane",
             "zipcode": "99999",
             "storeSize": "large",
             "storeVisitDate": "5/3/12",
             "action": "Purchase",
             "dollarAmount": "5.43"
           }
         ]
       },
       {
         "key": "12/31/12",
         "values": [
           {
             "storeID": "53454447",
             "storeName": "Ye Olde Candy Shoppe",
             "city": "Cityville",
             "building": "1313",
             "street": "Vampire Lane",
             "zipcode": "99999",
             "storeSize": "large",
             "storeVisitDate": "12/31/12",
             "action": "Purchase",
             "dollarAmount": "2.53"
           }
         ]
       },
       {
         "key": "1/24/13",
         "values": [
           {
             "storeID": "53454447",
             "storeName": "Ye Olde Candy Shoppe",
             "city": "Cityville",
             "building": "1313",
             "street": "Vampire Lane",
             "zipcode": "99999",
             "storeSize": "large",
             "storeVisitDate": "1/24/13",
             "action": "Return",
             "dollarAmount": "2.53"
           },
           {
             "storeID": "53454447",
             "storeName": "Ye Olde Candy Shoppe",
             "city": "Cityville",
             "building": "1313",
             "street": "Vampire Lane",
             "zipcode": "99999",
             "storeSize": "large",
             "storeVisitDate": "1/24/13",
             "action": "Return",
             "dollarAmount": "64.22"
           }
         ]
       }
     ]
   },
   {
     "storeName": "Mike's Bikes",
     "address": "2626 Aardvark Circle, Townsville NY 88888",
     "zipCode": "88888",
     "storeSize": "small",
     "visits": [
       {
         "key": "8/8/14",
         "values": [
           {
             "storeID": "24335234",
             "storeName": "Mike's Bikes",
             "city": "Townsville",
             "building": "2626",
             "street": "Aardvark Circle",
             "zipcode": "88888",
             "storeSize": "small",
             "storeVisitDate": "8/8/14",
             "action": "Purchase",
             "dollarAmount": "443.55"
           },
           {
             "storeID": "24335234",
             "storeName": "Mike's Bikes",
             "city": "Townsville",
             "building": "2626",
             "street": "Aardvark Circle",
             "zipcode": "88888",
             "storeSize": "small",
             "storeVisitDate": "8/8/14",
             "action": "Purchase",
             "dollarAmount": "34"
           },
           {
             "storeID": "24335234",
             "storeName": "Mike's Bikes",
             "city": "Townsville",
             "building": "2626",
             "street": "Aardvark Circle",
             "zipcode": "88888",
             "storeSize": "small",
             "storeVisitDate": "8/8/14",
             "action": "Purchase",
             "dollarAmount": "12.32"
           }
         ]
       },
       {
         "key": "10/3/15",
         "values": [
           {
             "storeID": "24335234",
             "storeName": "Mike's Bikes",
             "city": "Townsville",
             "building": "2626",
             "street": "Aardvark Circle",
             "zipcode": "88888",
             "storeSize": "small",
             "storeVisitDate": "10/3/15",
             "action": "Purchase",
             "dollarAmount": "233.1"
           },
           {
             "storeID": "24335234",
             "storeName": "Mike's Bikes",
             "city": "Townsville",
             "building": "2626",
             "street": "Aardvark Circle",
             "zipcode": "88888",
             "storeSize": "small",
             "storeVisitDate": "10/3/15",
             "action": "Return",
             "dollarAmount": "44.99"
           }
         ]
       }
     ]
   }
 ]

You're correct! You can do most of this with .map(). And the new ES6 standard makes a lot of this much easier, and with the below function you wont even modify any of the original data!:

array.map(store => {
  //return a new object that takes all the store info, then reassigns the visits key in a new object
  return Object.assign({}, store, {
    //map over visits, and reassign the key key to visitDate
    visits: store.visits.map(({ key: visitDate, values }) => {
      return {
        //return an obj with visit date
        visitDate,
        // do destructuring again to create objects of action,dollars
        transactions: values.map(({ action, dollarAmount: dollars }) => ({ action, dollars }))
      };
    })
  });
});

( working example on jsFiddle here - just open JS console to see the converted data set)

Few points about solution below:

  • it'll work even if your data has more keys that in a sample you pasted (you mentioned that this is a simplified set, so it might be important)
  • it uses "map" a lot, instead of manually iterating over arrays. I find it more readable.

     // Go over all stores stores.map(function(store) { // In each store, go over all visits. store.visits.map(function(visit) { // In each visit, copy 'key' to 'visitDate' // and 'values' to 'transactions'. // Then delete old names ('key' and 'values'). visit.visitDate = visit.key; visit.transactions = visit.values; delete visit.key; delete visit.values; // For each transaction, replace it with a simple // map with only 'action' and 'dollars'. visit.transactions = visit.transactions.map(function(tx) { return { action: tx.action, dollars: tx.dollarAmount }; }); }); }); 

Note that map() is supported in IE9 and newer.

To rename properties, you can create a new one with the same value then delete the old one.

You can use this for all parts, but looking at your structure, it looks neater to combine b,c,d together by using map() to create a new array of "optimised" transactions and assigning that to the new transactions property.

 // (I put this in a function so the logic can be at the top of the snippet) function fixData(stores) { // loop stores and their visits for (var iStore = 0; iStore < stores.length; iStore++) { var store = stores[iStore]; for (var iVisit = 0; iVisit < store.visits.length; iVisit++) { var visit = store.visits[iVisit]; // (a) rename property key to visitDate // add a new property with the same value then delete the old property visit.visitDate = visit.key; delete visit.key; // (b) rename property values to transactions // add a new property with the same value then delete the old property // (c) delete the 8 redundant properties (from storeID to storeVisitDate) // we could delete keys but quicker to map a new object // (d) rename property dollarAmount to dollars. // just give the new object property a different name visit.transactions = visit.values.map(function(trans) { return { action: trans.action, dollars: trans.dollarAmount } }); delete visit.values; } } console.log(stores); } var stores = [{ "storeName": "Ye Olde Candy Shoppe", "address": "1313 Vampire Lane, Cityville NY 99999", "zipCode": "99999", "storeSize": "large", "visits": [{ "key": "5/3/12", "values": [{ "storeID": "53454447", "storeName": "Ye Olde Candy Shoppe", "city": "Cityville", "building": "1313", "street": "Vampire Lane", "zipcode": "99999", "storeSize": "large", "storeVisitDate": "5/3/12", "action": "Return", "dollarAmount": "65.43" }, { "storeID": "53454447", "storeName": "Ye Olde Candy Shoppe", "city": "Cityville", "building": "1313", "street": "Vampire Lane", "zipcode": "99999", "storeSize": "large", "storeVisitDate": "5/3/12", "action": "Purchase", "dollarAmount": "12.43" }, { "storeID": "53454447", "storeName": "Ye Olde Candy Shoppe", "city": "Cityville", "building": "1313", "street": "Vampire Lane", "zipcode": "99999", "storeSize": "large", "storeVisitDate": "5/3/12", "action": "Purchase", "dollarAmount": "5.43" }] }, { "key": "12/31/12", "values": [{ "storeID": "53454447", "storeName": "Ye Olde Candy Shoppe", "city": "Cityville", "building": "1313", "street": "Vampire Lane", "zipcode": "99999", "storeSize": "large", "storeVisitDate": "12/31/12", "action": "Purchase", "dollarAmount": "2.53" }] }, { "key": "1/24/13", "values": [{ "storeID": "53454447", "storeName": "Ye Olde Candy Shoppe", "city": "Cityville", "building": "1313", "street": "Vampire Lane", "zipcode": "99999", "storeSize": "large", "storeVisitDate": "1/24/13", "action": "Return", "dollarAmount": "2.53" }, { "storeID": "53454447", "storeName": "Ye Olde Candy Shoppe", "city": "Cityville", "building": "1313", "street": "Vampire Lane", "zipcode": "99999", "storeSize": "large", "storeVisitDate": "1/24/13", "action": "Return", "dollarAmount": "64.22" }] }] }, { "storeName": "Mike's Bikes", "address": "2626 Aardvark Circle, Townsville NY 88888", "zipCode": "88888", "storeSize": "small", "visits": [{ "key": "8/8/14", "values": [{ "storeID": "24335234", "storeName": "Mike's Bikes", "city": "Townsville", "building": "2626", "street": "Aardvark Circle", "zipcode": "88888", "storeSize": "small", "storeVisitDate": "8/8/14", "action": "Purchase", "dollarAmount": "443.55" }, { "storeID": "24335234", "storeName": "Mike's Bikes", "city": "Townsville", "building": "2626", "street": "Aardvark Circle", "zipcode": "88888", "storeSize": "small", "storeVisitDate": "8/8/14", "action": "Purchase", "dollarAmount": "34" }, { "storeID": "24335234", "storeName": "Mike's Bikes", "city": "Townsville", "building": "2626", "street": "Aardvark Circle", "zipcode": "88888", "storeSize": "small", "storeVisitDate": "8/8/14", "action": "Purchase", "dollarAmount": "12.32" }] }, { "key": "10/3/15", "values": [{ "storeID": "24335234", "storeName": "Mike's Bikes", "city": "Townsville", "building": "2626", "street": "Aardvark Circle", "zipcode": "88888", "storeSize": "small", "storeVisitDate": "10/3/15", "action": "Purchase", "dollarAmount": "233.1" }, { "storeID": "24335234", "storeName": "Mike's Bikes", "city": "Townsville", "building": "2626", "street": "Aardvark Circle", "zipcode": "88888", "storeSize": "small", "storeVisitDate": "10/3/15", "action": "Return", "dollarAmount": "44.99" }] }] }]; fixData(stores); 

If we assume your array is called arr, I propose:

arr.forEach(function(currentValue, index, array) {
   currentValue.visits = currentValue.visits.map(function(currentValue, index, array) {
      currentValue.visitDate = currentValue.key;
      delete currentValue.key;
      currentValue.transactions = currentValue.values.map(function(currentValue, index, array) {
         currentValue = {action: currentValue.action, dollars: currentValue.dollarAmount};
         return currentValue;
      });
      delete currentValue.values;
      return currentValue;
   });
});

Well, since this is 'data' rather than some in-memory objects, I'm guessing you got it from JSON by using JSON.parse . (If not, you can still use this approach by first using JSON.strigify )

Did you know JSON.parse accepts an function to control such things? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse

So....

fixed=JSON.parse(dataString, function(key,value){
 if(key=="key"){this.visitDate=value;return}
 if(key=="values"){this.transactions=value;return}
 if(key=="somethingYouDontWant"){return;}
 //etc...
 return value;    
})

Its pure javascript without any library and uses simple map:

 var y = x.map(function(xs){
      xs.visits = xs.visits.map(function (item){
        return {
          visitDate: item.key,
          transactions: item.values.map(function(v){
            return {
              action: v.action,
              dollars: v.dollarAmount
            };
          })
        };
      });
      return xs;
    });

Flexible solution with Array.map() and delete operator:

var delete_props = ["storeID","storeName","city","building","street", "zipcode","storeSize", "storeVisitDate"];

 // arr is your initial array
 arr.map(function(obj){
     obj['visits'].map(function(inner_obj){
         inner_obj['visitDate'] = inner_obj['key'];
         delete inner_obj['key'];

         inner_obj['values'].map(function(values_obj){
            values_obj['dollars'] = values_obj['dollarAmount'];
            delete values_obj['dollarAmount'];

            delete_props.forEach(function(v){
                delete values_obj[v];
            });
        });
        inner_obj['transactions '] = inner_obj['values'];
        delete inner_obj['values'];

     });     
 });

here is another solution, the code is pretty straightforward but not optimized.

var obj = {}, key;
data.forEach(item => {
  item.visits.forEach(visit => {
    visit.visitDate = visit.key; 
    delete visit.key;
    visit.transactions = [];
    visit.values.forEach(value => {
      obj = {};
      for (key in value) {
        if (value.hasOwnProperty(key) && retain.indexOf(key) > -1) {
          obj[key] = value[key];
        }
      }
      visit.transactions.push(obj);
    });
    delete visit.values;
  });
console.log(item);
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM