-1

I'm trying to pull out specific fields from backend data to prep the body of a table. The data coming in has the structure of:

[
{
_id: "63056cee252b83f4bc8f97e9",
goals: [
{ title: "Cook" },
{ title: "Budget" }
],
visitEnd: "2022-08-18T00:30:00.000Z",
visitStart: "2022-08-17T21:30:00.000Z",
},
{
_id: "63223586798c6b2658a0d576",
goals: [
{ title: "Cook" },
{ title: "Budget" },
{ title: "Clean" }
],
visitEnd: "2022-09-13T00:30:00.000Z",
visitStart: "2022-09-12T22:00:00.000Z"
},
{
_id: "63542ecfca5bd097a0d9acaf",
goals: [
{ title: "Cook" },
{ title: "Clean" }
],
visitEnd: "2022-10-12T19:00:11.000Z",
visitStart: "2022-10-12T17:00:00.000Z",
}]

Since the table headers are by month/year, I'm using lodash to group them by month, which gets me here:

Object { 7: (2) […], 8: (2) […], 9: (2) […] }
​
7: Array [ {…}, {…} ]
​​
0: Object { user: "62410a1dcaac9a3d0528de7a", location: "Firm Office in LA", visitStart: "2022-08-17T21:30:00.000Z", … }
​​
1: Object { user: "62410a1dcaac9a3d0528de7a", location: "place", visitStart: "2022-08-11T21:00:57.000Z", … }
​​
length: 2
​​
<prototype>: Array []
​
8: Array [ {…}, {…} ]
​​
0: Object { user: "62410a1dcaac9a3d0528de7a", location: "Home", visitStart: "2022-09-12T22:00:00.000Z", … }
​​
1: Object { user: "62410a1dcaac9a3d0528de7a", location: "place", visitStart: "2022-09-21T21:00:00.000Z", … }
​​
length: 2
​​
<prototype>: Array []
​
9: Array [ {…}, {…} ]
​​
0: Object { user: "62410a1dcaac9a3d0528de7a", location: "Home", visitStart: "2022-10-12T17:00:00.000Z", … }
​​
1: Object { user: "62410a1dcaac9a3d0528de7a", location: "place", visitStart: "2022-10-21T21:00:00.000Z", … }
​​
length: 2

But now I'm stuck since I want to isolate the fields of the goals array, which is within the objects, within the array of each month, which is contained in an object. I've tried playing around with Object.keys and maps, and then from here: https://dev.to/flexdinesh/accessing-nested-objects-in-javascript--9m4 came across a function to get deeply nested items. But I'm still messing this up, and my head is spinning trying to make sense of it. I looked at lodash's map and property, but was not sure how to implement given the layers of nesting I'm trying to work through on dynamically named arrays within the groupBy object. Heres where I'm at, but I'm getting the error i.map is not a function

const sort = groupBy(visits, ({visitEnd})=> new Date(visitEnd).getMonth());
   console.log("sort 1: ", sort)
   const stage = Object.keys(sort).map((i) => {
      { i.map((el) => getNestedObject(el, ['goals', 'title'])) }
      })
   console.log("sort 2: ", stage)

My javascript knowledge is terrible which doesn't help...

Jowz
  • 399
  • 1
  • 3
  • 16

1 Answers1

1

The error you're getting, i.map is not a function, means that the variable i is not an array. Based on the data you supplied in your post i is an object.

Iterate the result of the sorted month/year data using Object.entries() versus Object.keys().

To get a list of unique goals per month with output that looks like:

{
    7: ["Cook", "Spend", "Clean"],
    8: ["Cook", "Budget", "Clean"],
    9: ["Cook", "Budget", "Scrub", "Fold", "Rest", "Wash"]
}

const dataSortedByMoYrObj = {
  7: [{
      user: "62410a1dcaac9a3d0528de7a",
      location: "Firm Office in LA",
      visitStart: "2022-08-17T21:30:00.000Z",
      goals: [{
          title: ""
        },
        {
          title: "Spend"
        },
        {
          title: "Clean"
        }
      ]
    },
    {
      user: "62410a1dcaac9a3d0528de7a",
      location: "place",
      visitStart: "2022-08-11T21:00:57.000Z",
      goals: [{
          title: "Cook"
        },
        {
          title: undefined
        }
      ]
    }
  ],
  8: [{
      user: "62410a1dcaac9a3d0528de7a",
      location: "Home",
      visitStart: "2022-09-12T22:00:00.000Z",
      goals: [{
          title: "Cook"
        },
        {
          title: "Budget"
        },
        {
          title: null
        }
      ]
    },
    {
      user: "62410a1dcaac9a3d0528de7a",
      location: "place",
      visitStart: "2022-09-21T21:00:00.000Z"
    }
  ],
  9: [{
      user: "62410a1dcaac9a3d0528de7a",
      location: "Home",
      visitStart: "2022-10-12T17:00:00.000Z",
      goals: [{
          title: "Cook"
        },
        {
          title: "Budget"
        },
        {
          title: "Scrub"
        }
      ]
    },
    {
      user: "62410a1dcaac9a3d0528de7a",
      location: "place",
      visitStart: "2022-10-21T21:00:00.000Z",
      goals: [{
          title: "Fold"
        },
        {
          title: "Rest"
        },
        {
          title: "Wash"
        }
      ]
    }
  ]
};

// 'const getNestedObject' code sourced from:
// https://dev.to/flexdinesh/accessing-nested-objects-in-javascript--9m4
const getNestedObject = (nestedObj, pathArr) => {
  return pathArr.reduce((obj, key) =>
    (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}

const goalsByMonthYearObj = {};
Object.entries(dataSortedByMoYrObj).forEach(([month, users]) => {
  // 'month' represents the key.
  // 'users' is an array of 'user' objects listed for each month.
  let goalsByMonth = [];
  users.forEach(user => {
    const goalsProp = getNestedObject(user, ['goals']);
    // Check if the 'goals' property is a valid.
    // If 'goals' property is 'null' or 'undefined',
    // '!Array.isArray(null)' returns 'true'.
    if (!Array.isArray(goalsProp)) {
      return;
    }
    // Convert list of goal objects (e.g. '{title: Budget}')
    // to an array using 'goalsProp.map()'.
    // Use the 'filter()' method first to remove empty strings
    // 'null' and 'undefined' values in the 'title' property.
    // https://stackoverflow.com/questions/24806772/how-to-skip-over-an-element-in-map
    const goalsArr = goalsProp.filter(goal => goal.title).map(goal => goal.title);
    // Concatenate goals array to the existing goals-by-month array.
    goalsByMonth = goalsByMonth.concat(goalsArr);
  });
  // Sort array of goals alphabetically
  // https://stackoverflow.com/a/45544166
  goalsByMonth.sort((a, b) => a.localeCompare(b));
  // Add array of unique goals for each month
  // https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates
  goalsByMonthYearObj[month] = [...new Set(goalsByMonth)];
});
console.log(goalsByMonthYearObj);

(Original code that's not as concise as above snippet.)

const goalsByMonthYearObj = {};

// Reference to 'Object.entries()' at:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
for (const [key, value] of Object.entries(dataSortedByMoYrObj)) {
    // 'key' represents a month index.
    // 'value' contains an array of objects listed for each month index.
    //console.log(`${key}: ${value}`);

    const goalsByMonth = [];

    value.forEach(item => {
        // The 'goals' property is only one level deep so
        // it's not necessary to use the 'getNestedObject()'
        // function. 
        // For example: const goalsProp = item.goals;
        // The function is useful for more deeply
        // embedded properties. 
        const goalsProp = getNestedObject(item, ['goals']);

        if (!Array.isArray(goalsProp)) { return; }

        goalsProp.forEach(goal => {
            if (!goal.title) { return; }
            goalsByMonth.push(goal.title);
        });
    });

    // https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates
    const uniqueGoals = [...new Set(goalsByMonth)];

    goalsByMonthYearObj[key] = uniqueGoals;
}

console.log(goalsByMonthYearObj);
Dave B
  • 1,105
  • 11
  • 17
  • Thanks, staring at these too long I lose focus and didnt catch that the month entries were another layer of objects of arrays. My goal is to isolate the value of the goals arrays to the month object, so {7: ["cook", "budget", "clean", "cook", "clean", "budget"]}. Ultimately I'll need to separate out the values per month and run a reduce on them. I'm more used to arrays, dialing down in objects without clear key/values is hurting my brain – Jowz Feb 18 '23 at 18:00
  • The objective of your code is now clear. I updated the code snippet. – Dave B Feb 18 '23 at 20:31
  • Thank you so much! I appreciate all the explanations in the commented lines, really helps to make sense of how the syntax comes together. – Jowz Feb 20 '23 at 18:24
  • 1
    Updated code snippet: `users.map()` was not necessary as a new (mapped) array is not being created. It was replaced with `users.forEach()`. The `filter()` was applied to check for empty strings, `null` or `undefined` goal title property values. – Dave B Feb 22 '23 at 18:01
  • Much appreciated, was just looking into that aspect. Again thank you for all the help! – Jowz Feb 22 '23 at 21:46