3

I am trying to create an array, where each "Working Day" is an object with an index and start/end date but I have no clue how to manipulate the JSON to have a specific structure.

I am using the following package: https://github.com/SheetJS/sheetjs

Current code:

const workbook = XLSX.read(file.buffer, { type: 'buffer' });
const worksheet = workbook.Sheets['Calendar Config'];
const parsed = XLSX.utils.sheet_to_json(worksheet);

Current output:

[
  {
    'Calendar Name': 'Standard',
    'Valid From': 44197,
    'Valid To': 44561,
    'Use Holidays': 'yes',
    'Working Day': 'Monday',
    Start: 0.3333333333333333,
    End: 0.8333333333333334
  },
  {
    'Working Day': 'Tuesday',
    Start: 0.3333333333333333,
    End: 0.8333333333333334
  },
  {
    'Working Day': 'Wednesday',
    Start: 0.3333333333333333,
    End: 0.8333333333333334
  },
  {
    'Working Day': 'Thursday',
    Start: 0.3333333333333333,
    End: 0.8333333333333334
  },
  {
    'Working Day': 'Friday',
    Start: 0.3333333333333333,
    End: 0.8333333333333334
  },
  { 'Working Day': 'Saturday', Start: '-', End: '-' },
  { 'Working Day': 'Sunday', Start: '-', End: '-' }
]

Desired JSON:

{
 "name": "Standard",
 "validFrom": "2021-01-01T00:00:00.000Z",
 "validTo": "2021-12-31T00:00:00.000Z",
 "useHolidays": true,
 "workingDays": [
  {
    "dayIndex": 0,
    "dayStart": "8:00",
    "dayEnd": "20:00"
  },
  {
    "dayIndex": 1,
    "dayStart": "8:00",
    "dayEnd": "20:00"
  },
  ...
  {
    "dayIndex": 6,
    "dayStart": "-",
    "dayEnd": "-"
  },
 ],
 "uploadedBy": "foo"
}

Parsed Excel Sheet:

excel screenshot

1 Answer 1

5

Consider, if your spreadsheet was like this - parsing each row (and ignoring headers) would allow you to generate the desired output with greater ease:

1

You can transpose the sheet_to_json output to achieve this. Refer to this issue. Note sheet_to_json is called with {header: 1}:

const XLSX = require("xlsx");
const filename = "./Book1.xlsx";
const workbook = XLSX.readFile(filename);
const worksheet = workbook.Sheets["Calendar Config"]; 
const rowMajor = XLSX.utils.sheet_to_json(worksheet, {header: 1});

// transpose from row-major to column-major
// https://github.com/SheetJS/sheetjs/issues/1729
const rawTransposed = [];
for (let i=0; i<rowMajor.length; i++) {
  for (let j=0; j<rowMajor[i].length; j++) {
    if (!rawTransposed[j]) rawTransposed[j] = [];
    rawTransposed[j][i] = rowMajor[i][j]; 
  }
}

// clean out undefineds
const transposed = rawTransposed.map(arr => arr.filter(k => !!k));

console.log(transposed);

Which will give you this:

[
  [ 'Calendar Name', 'Standard' ],
  [ 'Valid From', 44197 ],        
  [ 'Valid To', 44561 ],
  [ 'Use Holidays', 'yes' ],      
  [
    'Working Day',
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    'Saturday',
    'Sunday'
  ],
  [
    'Start',
    0.3333333333333333,
    0.3333333333333333,
    0.3333333333333333,
    0.3333333333333333,
    0.3333333333333333,
    '-',
    '-'
  ],
  [
    'End',
    0.8333333333333334,
    0.8333333333333334,
    0.8333333333333334,
    0.8333333333333334,
    0.8333333333333334,
    '-',
    '-'
  ]
]

It's then more straight-forward to transform this array into the desired object e.g. you are converting yes to true and converting dates and blending the work days etc.

const XLSX = require("xlsx");
const filename = "./Book1.xlsx";
const workbook = XLSX.readFile(filename);
const worksheet = workbook.Sheets["Calendar Config"]; 
const rowMajor = XLSX.utils.sheet_to_json(worksheet, {header: 1});

// transpose from row-major to column-major
// https://github.com/SheetJS/sheetjs/issues/1729
const rawTransposed = [];
for (let i=0; i<rowMajor.length; i++) {
  for (let j=0; j<rowMajor[i].length; j++) {
    if (!rawTransposed[j]) rawTransposed[j] = [];
    rawTransposed[j][i] = rowMajor[i][j]; 
  }
}

// clean out undefineds
const transposed = rawTransposed.map(arr => arr.filter(k => !!k));

// console.log(transposed);

// https://stackoverflow.com/questions/16229494/converting-excel-date-serial-number-to-date-using-javascript
function xlDateConvert(xlIndex) {
  const d = new Date(Math.round(xlIndex - 25569) * 86400000);
  return d.toISOString();
}

function xlTimeConvert(xlIndex, utcOffset) {
  const hours = Math.floor((xlIndex % 1) * 24);
  const minutes = Math.floor((((xlIndex % 1) * 24) - hours) * 60)
  const d = new Date(Date.UTC(0, 0, xlIndex, hours - utcOffset, minutes));
  return d.toLocaleTimeString("en-IT", {hour: "2-digit", minute:"2-digit", hour12: false});
}

// create custom object
const index = Array.from({length: 5}, (k, i) => i); // 5 keys in object
const output = index.reduce((acc, curr, idx) => {
  switch (curr) {
    case 0: // name 
      acc["name"] = transposed[idx].slice(1)[0];
      break;
    case 1: // validFrom
      acc["validFrom"] = xlDateConvert(transposed[idx][1]);
      break;
    case 2: // validTo
      acc["validTo"] = xlDateConvert(transposed[idx][1]);
      break;
    case 3: // useHolidays
      acc["useHolidays"] = transposed[idx][1] === "yes" ? true : false;
      break;
    case 4: // workingDays
      acc["workingDays"] = transposed[idx].slice(1).map((arr, i) => {
        const start = transposed[idx + 1][i + 1];
        const end = transposed[idx + 2][i + 1];
        const dayStart = start === "-" ? start : xlTimeConvert(start, 10);
        const dayEnd = end === "-" ? end : xlTimeConvert(end, 10);
        return {
          dayIndex: i,
          dayStart: dayStart,
          dayEnd: dayEnd
        }
      })
    default:
      break;
  }
  return acc;
}, {});

// some custom property
output["uploadedBy"] = "foo";

// output
console.log(output);

Will output:

{
  name: 'Standard',
  validFrom: '2021-01-01T00:00:00.000Z',
  validTo: '2021-12-31T00:00:00.000Z',
  useHolidays: true,
  workingDays: [
    { dayIndex: 0, dayStart: '08:00', dayEnd: '20:00' },
    { dayIndex: 1, dayStart: '08:00', dayEnd: '20:00' },
    { dayIndex: 2, dayStart: '08:00', dayEnd: '20:00' },
    { dayIndex: 3, dayStart: '08:00', dayEnd: '20:00' },
    { dayIndex: 4, dayStart: '08:00', dayEnd: '20:00' },
    { dayIndex: 5, dayStart: '-', dayEnd: '-' },
    { dayIndex: 6, dayStart: '-', dayEnd: '-' }
  ],
  uploadedBy: 'foo'
}
Sign up to request clarification or add additional context in comments.

4 Comments

I have to admit... I absolutely did not expect such an detailed and excellent answer! Thank you very much!
The only problem is that the JSON looks in my case (worksheet looks exactly like yours) like this: pastebin.com/rHzU6H5R So the workinDays case is not working for me and I don't know how to fix this right now. Do you think you can help me out with that too? Cheers!
Hi @mrks. The JSON in your pastebin looks like what you get from XLSX.utils.sheet_to_json(worksheet, {header: 1});. The code for transpose starts from const rowTansposed... and below in the example. That should 'flip' the rows and columns around and make the output object easier to generate.
What I was trying to get at in the answer is that there's no option in sheet_to_json to do the 'flip' so you have to do that yourself. Your Excel data is structured by columns and SheetJs is normally just expecting data structured by row. But its just a few lines of code to do this flip/ transpose operation. HTH.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.