Home > Software design >  Accessing nested dictionaries within a dictionary in Javascript
Accessing nested dictionaries within a dictionary in Javascript

Time:09-17

I'm looking to create a calendar that retrieves a holiday based on the current date for my school. Presently, I have the data structured as a dictionary with dictionaries within it.

const holidays = {
  January: {
    "Martin Luther King Jr. Day": [17]
  },
  February: {},
  March: {
    "Spring Break": [16]
  },
  April: {},
  May: {},
  June: {},
  July: {
    "Fourth of July": [4]
  },
  August: {},
  September: {},
  October: {},
  November: {
    "Thanksgiving": [25, 26]
  },
  December: {
    "Christmas Eve": [23],
    "Christmas": [25],
    "Winter Break": [26, 27, 28, 29, 30, 31]
  }
};

My current approach is that it would check the month, then the array for the date. If the date exists in the array, it would pull the key value to show what holiday it is on the school hours webpage. I've tried different ways to search the child object's arrays, but none have been successful.

This is my attempt:

function testCase() {
  for (var k in holidays) {
    if (x) {
      let y = holidays[x];
      for (var j in y) {
        if (date == y[j]) {
          return a.innerHTML = Object.keys(j[x]);
        } else {
          return a.innerHTML = "NO FIND";
        }
      }
    }
  }
}
```

CodePudding user response:

Get the current month name and date from a Date object.

You can use the month name as an index into the holidays object. Then you can loop over the keys of that object, using includes() to test if the current date is in that array, and return the holiday name.

If you never return within the loop, you return a default value at the end.

const monthNames = ["January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"];

function testCase() {
  const today = new Date();
  const curMonth = monthNames[today.getMonth()];
  const curDate = today.getDate();
  let holidayMonth = holidays[curMonth];
  for (let holiday in holidayMonth) {
    if (holidays[holiday].includes(curDate)) {
      return holiday;
    }
  }
  return "No holiday found";
}

CodePudding user response:

I'll go out on a limb and say your data structure is a tad complicated for what you probably need, and if you are in control of that, then I would suggest you start by simplifying to something like this:

const holidays = [
  {name: "Christmas", startFrom: new Date("2021-12-25T00:00:00Z"), endAt: new Date("2021-12-25T23:59:59Z")}
  ... // the other dates in your list
]

By doing this, you have a structure that can handle a range of days, with the month included in those dates. You now only need to iterate over one dimension, which should make your code simpler.

It helps to consider that all holidays are not fixed each year. Easter for example, is not always on the same day (or month for that matter), with absolute time ranges, you can define the same holiday for the following year in your list.

Next, you could define a function that takes a date input, and compare the dates directly:


function FindHoliday(date) { // a Date Type input parameter
  return holidays.filter(({startFrom, endAt}) => startFrom <= date && endAt >= date)
} // returns all holidays that fit in this range.

The array filter function returns a new array with all members that evaluate to true in the given evaluator function, so if you get an empty list, no holidays exist

From here, if you wanted to just write to the DOM...


a.innerHTML = FindHolidays(new Date("2021-12-25T12:00:00Z")).map(({name}) => name).join(", ")

CodePudding user response:

UPDATE
I have added a datepicker to my solution below so you can switch the date and see how the function responds.

It may be a better idea to break down the data using the date numbers as the keys and the holiday(s) as the values, which would be especially helpful since you can check for a key value of the current date number rather than looping through each Object key to see which, if any, contain the current date.

One other important note is that plain JS objects do not respect and retain insertion order, so it would be better to use an array of objects, one object per month. Not only does using an array of objects respect insertion order, but it uses zero-indexing (meaning the first item has an index of 0), which is also how the Date .getMonth() method works, where the method returns 0 if the current month is January and 11 if the current month is December. This also allows us to easily define multi-holiday days using arrays.

With this in mind, we can still use JS comments to reference month names. In the example below, I set the default value of the date input to Christmas 2021-12-25 and included both Christmas and Winter Break on that day so you can see the function works for multiple holidays.

const holidayStatus = document.getElementById('holiday-status'),
      dateInput = document.querySelector('input[type="date"]');

const holidays = [
  {   // January
     1: "New Years Day",
    17: "Martin Luther King Jr. Day"
  },
  {}, // February
  {   // March
    16: "Spring Break"
  },
  {}, // April
  {}, // May
  {}, // June
  {   // July
     4: "Fourth of July"
  },
  {}, // August
  {}, // September
  {}, // October
  {   // November
    25: "Thanksgiving",
    26: "Thanksgiving"
  },
  {   // December
    24: ["Christmas Eve", "Winter Break"],
    25: ["Christmas", "Winter Break"],
    26: "Winter Break",
    27: "Winter Break",
    28: "Winter Break",
    29: "Winter Break",
    30: "Winter Break",
    31: ["New Years Eve", "Winter Break"]
  },
];

const combineStrings = strs => (strs = [].concat(strs), strs.length === 1 ? strs[0] : (strs.length === 2 ? strs.join(' and ') : strs.map((str, i) => i === strs.length - 1 ? 'and '   str : str).join(', ')));

const getDate = () => new Date(dateInput.value   ' 12:00');

const printHolidays = date => {
  const todaysHolidays = holidays[date.getMonth()][date.getDate()];
  holidayStatus.textContent = todaysHolidays ? (`This day's ${typeof todaysHolidays === 'string' || todaysHolidays.length === 1 ? 'holiday is' : 'holdays are'} ${combineStrings(todaysHolidays)}.`) : 'This day has no holidays.';
};
printHolidays(getDate());

dateInput.addEventListener('change', () => {
  printHolidays(getDate());
});
#holiday-status {
  display: inline-block;
  margin-top: 5px;
  padding: 5px 10px;
  border: 2px solid #f00;
  box-sizing: border-box;
}
<div>Using the date: <input type="date" value="2021-12-25"></div><span id="holiday-status"></span>

The helper function combineStrings I added dynamically combines multiple strings if an array exists and returns the passed string if a simple string was passed.

Here are a few examples of what different strings and arrays of strings would return:

combineStrings("Holiday 1")
// -> "Holiday 1"
combineStrings(["Holiday 1", "Holiday 2"])
// -> "Holiday 1 and Holiday 2"
combineStrings(["Holiday 1", "Holiday 2", "Holiday 3"])
// -> "Holiday 1, Holiday 2, and Holiday 3"
  • Related