read-excel-file 8.0.2 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ 9.0.0 / 18.04.2026
2
+ ==================
3
+
4
+ * Refactored `parseData()` function.
5
+ * The result of `parseData()` function is now `{ errors, objects }`. If there're no errors, `errors` will be `undefined`. Otherwise, `errors` will be a non-empty array and `objects` will be `undefined`.
6
+ * Previously the result of `parseData()` function was `[{ errors, object }, ...]`, i.e. the `errors` were split between each particular data row. Now the `errors` are combined for all data rows. The rationale is that it's simpler to handle the result of the function this way.
7
+ * In a schema, a nested object is now not allowed to be `required: true`. Otherwise, if a nested object was allowed to be `required: true`, a corresponding `"required"` error would have to include a specific `column` title but a nested object simply doesn't have one.
8
+
1
9
  8.0.0 / 11.03.2026
2
10
  ==================
3
11
 
@@ -26,11 +34,12 @@
26
34
  * `getEmptyArrayValue` → `transformEmptyArray`
27
35
  * The leading `.` character is now removed from the `path` parameter.
28
36
  * Previously, when parsing comma-separated values, it used to ignore any commas that're surrounded by quotes, similar to how it's done in `.csv` files. Now it no longer does that.
37
+ * Previously, when parsing comma-separated values, it used to allow empty-string elements. Now it no longer does that and such empty-string elements will now result in an error with properties: `{ error: "invalid", reason: "syntax" }`.
29
38
  * Previously, when parsing using a schema, it used to force-convert all `type: Date` schema properties from any numeric cell value to a `Date` with a given timestamp. Now it demands the cell values for all such `type: Date` schema properties to already be correctly recognized as `Date`s when they're returned from `readSheet()` or `readExcelFile()` function. And I'd personally assume that in any sane (non-contrived) real-world usage scenario that would be the case, so it doesn't really seem like a "breaking change". And if, for some strange reason, that happens not to be the case, `parseData()` function will throw an error: `not_a_date`.
30
39
  * Previously, when parsing using a schema, it used to skip `required` validation for completely-empty rows. It no longer does that.
31
40
  * Removed exported function `parseExcelDate()` because there seems to be no need to have it exported.
32
41
  * (TypeScript) Renamed exported types:
33
- * `Type` → `ParseDataValueType`
42
+ * `Type` → `ParseDataCustomType`
34
43
  * `Error` or `SchemaParseCellValueError` → `ParseDataError`
35
44
  * `CellValueRequiredError` → `ParseDataValueRequiredError`
36
45
  * `ParsedObjectsResult` → `ParseDataResult`
package/README.md CHANGED
@@ -37,6 +37,7 @@ Also check out [`write-excel-file`](https://www.npmjs.com/package/write-excel-fi
37
37
  * The `result` of the function is an array where each element represents a "data row" and has shape `{ object, errors }`.
38
38
  * Depending on whether there were any errors when parsing a given "data row", either `object` or `errors` property will be `undefined`.
39
39
  * The `errors` don't have a `row` property anymore because it could be derived from "data row" number.
40
+ * In version `9.x`, the returned result of `parseData()` has been changed back to `{ errors, objects }`, so consider migrating straight to `9.x`. In that case, if there're no errors, `errors` will be `undefined`; otherwise, `errors` will be a non-empty array and `objects` will be `undefined`.
40
41
  * Removed `transformData` parameter because `schema` parameter was removed. A developer could transform the `data` themself and then pass it to `parseData()` function.
41
42
  * Removed `isColumnOriented` parameter.
42
43
  * Removed `ignoreEmptyRows` parameter. Empty rows somewhere in the middle are not ignored now.
@@ -49,16 +50,28 @@ Also check out [`write-excel-file`](https://www.npmjs.com/package/write-excel-fi
49
50
  * `getEmptyArrayValue` → `transformEmptyArray`
50
51
  * The leading `.` character is now removed from the `path` parameter.
51
52
  * Previously, when parsing comma-separated values, it used to ignore any commas that're surrounded by quotes, similar to how it's done in `.csv` files. Now it no longer does that.
53
+ * Previously, when parsing comma-separated values, it used to allow empty-string elements. Now it no longer does that and such empty-string elements will now result in an error with properties: `{ error: "invalid", reason: "syntax" }`.
52
54
  * Previously, when parsing using a schema, it used to force-convert all `type: Date` schema properties from any numeric cell value to a `Date` with a given timestamp. Now it demands the cell values for all such `type: Date` schema properties to already be correctly recognized as `Date`s when they're returned from `readSheet()` or `readExcelFile()` function. And I'd personally assume that in any sane (non-contrived) real-world usage scenario that would be the case, so it doesn't really seem like a "breaking change". And if, for some strange reason, that happens not to be the case, `parseData()` function will throw an error: `not_a_date`.
53
55
  * Previously, when parsing using a schema, it used to skip `required` validation for completely-empty rows. It no longer does that.
54
56
  * Removed exported function `parseExcelDate()` because there seems to be no need to have it exported.
55
57
  * (TypeScript) Renamed exported types:
56
- * `Type` → `ParseDataValueType`
58
+ * `Type` → `ParseDataCustomType`
57
59
  * `Error` or `SchemaParseCellValueError` → `ParseDataError`
58
60
  * `CellValueRequiredError` → `ParseDataValueRequiredError`
59
61
  * `ParsedObjectsResult` → `ParseDataResult`
60
62
  </details>
61
63
 
64
+ <details>
65
+ <summary>Migrating from <code>8.x</code> to <code>9.x</code></summary>
66
+
67
+ ######
68
+
69
+ * Refactored `parseData()` function.
70
+ * The result of `parseData()` function is now `{ errors, objects }`. If there're no errors, `errors` will be `undefined`. Otherwise, `errors` will be a non-empty array and `objects` will be `undefined`.
71
+ * Previously the result of `parseData()` function was `[{ errors, object }, ...]`, i.e. the `errors` were split between each particular data row. Now the `errors` are combined for all data rows. The rationale is that it's simpler to handle the result of the function this way.
72
+ * In a schema, a nested object is now not allowed to be `required: true`. Otherwise, if a nested object was allowed to be `required: true`, a corresponding `"required"` error would have to include a specific `column` title but a nested object simply doesn't have one.
73
+ </details>
74
+
62
75
  ## Install
63
76
 
64
77
  ```js
@@ -71,6 +84,11 @@ Alternatively, it could be included on a web page [directly](#cdn) via a `<scrip
71
84
 
72
85
  If your `.xlsx` file only has a single "sheet", or if you only care for a single "sheet", or if you don't know or care what a "sheet" is, use `readSheet()` function.
73
86
 
87
+ | Name | Date of Birth | Married | Kids |
88
+ | ---------- | ------------- | ------- | ---- |
89
+ | John Smith | 1/1/1995 | TRUE | 3 |
90
+ | Kate Brown | 3/1/2010 | FALSE | 0 |
91
+
74
92
  ```js
75
93
  import { readSheet } from 'read-excel-file/node'
76
94
 
@@ -78,9 +96,9 @@ await readSheet(file)
78
96
 
79
97
  // Returns
80
98
  [
81
- ['John Smith',35,true,...],
82
- ['Kate Brown',28,false,...],
83
- ...
99
+ ['Name', 'Date of Birth', 'Married', 'Kids'],
100
+ ['John Smith', 1995-01-01T00:00:00.000Z, true, 3],
101
+ ['Kate Brown', 2010-03-01T00:00:00.000Z, false, 0]
84
102
  ]
85
103
  ```
86
104
 
@@ -101,9 +119,9 @@ await readExcelFile(file)
101
119
  [{
102
120
  sheet: 'Sheet1',
103
121
  data: [
104
- ['John Smith',35,true,...],
105
- ['Kate Brown',28,false,...],
106
- ...
122
+ ['Name', 'Age'],
123
+ ['John Smith', 30],
124
+ ['Kate Brown', 15]
107
125
  ]
108
126
  }, {
109
127
  sheet: 'Sheet2',
@@ -115,7 +133,9 @@ At least one "sheet" always exists. Each "sheet" is an object with properties:
115
133
  * `sheet` — Sheet name.
116
134
  * Example: `"Sheet1"`
117
135
  * `data` — Sheet data. An array of rows. Each row is an array of values — `string`, `number`, `boolean` or `Date`.
118
- * Example: `[ ['John Smith',35,true,...], ['Kate Brown',28,false,...], ... ]`
136
+ * Example: `[ ['Name','Age'], ['John Smith',30], ['Kate Brown',15] ]`
137
+
138
+ ## API
119
139
 
120
140
  This package provides a separate `import` path for each different environment, as described below.
121
141
 
@@ -280,16 +300,15 @@ import { readSheet, parseData } from "read-excel-file/browser"
280
300
 
281
301
  const data = await readSheet(file)
282
302
  const schema = { ... }
283
- for (const { object, errors } of parseData(data, schema)) {
284
- if (errors) {
285
- console.error(errors)
286
- } else {
287
- console.log(object)
288
- }
303
+ const { objects, errors } = parseData(data, schema)
304
+ if (errors) {
305
+ console.error(errors)
306
+ } else {
307
+ console.log(objects)
289
308
  }
290
309
  ```
291
310
 
292
- The `parseData()` function returns an array where each element represents a "data row" and has shape `{ object, errors }`. Depending on whether there were any errors when parsing a given "data row", either `object` or `errors` property will be `undefined`.
311
+ The `parseData()` function returns an object `{ objects, errors }`. Depending on whether there were any errors when parsing the data, either `objects` or `errors` property will be `undefined`.
293
312
 
294
313
  The sheet data that is being parsed should adhere to a simple structure: the first row should be a header row with just column titles, and each following row should specify the values for those columns.
295
314
 
@@ -351,107 +370,227 @@ Example:
351
370
 
352
371
  ```js
353
372
  // An example .xlsx document:
354
- // -----------------------------------------------------------------------------------------
355
- // | START DATE | NUMBER OF STUDENTS | IS FREE | COURSE TITLE | CONTACT | STATUS |
356
- // -----------------------------------------------------------------------------------------
357
- // | 03/24/2018 | 10 | true | Chemistry | (123) 456-7890 | SCHEDULED |
358
- // -----------------------------------------------------------------------------------------
373
+ // --------------------------------------------------------------------------------------------------------
374
+ // | START DATE | SEATS | STATUS | CONTACT | COURSE TITLE | COURSE CATEGORY | COURSE IS FREE |
375
+ // --------------------------------------------------------------------------------------------------------
376
+ // | 03/24/2018 | 10 | SCHEDULED | (123) 456-7890 | Basic Algebra | Math, Arithmetic | TRUE |
377
+ // --------------------------------------------------------------------------------------------------------
359
378
 
360
379
  const schema = {
361
- date: {
380
+ startDate: {
362
381
  column: 'START DATE',
363
382
  type: Date
364
383
  },
365
- numberOfStudents: {
366
- column: 'NUMBER OF STUDENTS',
384
+ seats: {
385
+ column: 'SEATS',
367
386
  type: Number,
368
387
  required: true
369
388
  },
370
- // Nested object example.
371
- course: {
372
- schema: {
373
- isFree: {
374
- column: 'IS FREE',
375
- type: Boolean
376
- },
377
- title: {
378
- column: 'COURSE TITLE',
379
- type: String
380
- }
381
- }
382
- // required: true/false
383
- },
384
- contact: {
385
- column: 'CONTACT',
386
- required: true,
387
- // A custom `type` parsing function can be specified.
388
- // It will parse the cell value if it's not empty.
389
- type: (value) => {
390
- const number = parsePhoneNumber(value)
391
- if (!number) {
392
- throw new Error('invalid')
393
- }
394
- return number
395
- }
396
- },
397
389
  status: {
398
390
  column: 'STATUS',
399
391
  type: String,
392
+ // An example of using `oneOf`
400
393
  oneOf: [
401
394
  'SCHEDULED',
402
395
  'STARTED',
403
396
  'FINISHED'
404
397
  ]
398
+ },
399
+ contact: {
400
+ column: 'CONTACT',
401
+ required: true,
402
+ // An example of using a custom `type`
403
+ type: PhoneNumber
404
+ },
405
+ // Nested object example
406
+ course: {
407
+ // A nested object could be declared as completely optional by specifying `required: false`.
408
+ // In that case, when all of its properties are missing from the input data, it wouldn't throw any error
409
+ // regardless of whether some of its properties are declared as `required: true` or not.
410
+ required: false,
411
+ schema: {
412
+ title: {
413
+ column: 'COURSE TITLE',
414
+ type: String,
415
+ // When course data is present, the course title must be specified.
416
+ required: true
417
+ },
418
+ categories: {
419
+ column: 'COURSE CATEGORY',
420
+ // An example of parsing comma-separated values.
421
+ type: [String]
422
+ },
423
+ isFree: {
424
+ column: 'COURSE IS FREE',
425
+ type: Boolean
426
+ }
427
+ }
405
428
  }
406
429
  }
407
430
 
431
+ // If this code was written in TypeScript, `schema` would've been declared as:
432
+ // const schema: Schema<Object, ColumnTitle> = { ... }
433
+
434
+ // Read `data` from an `.xlsx` file
408
435
  const data = await readSheet(file)
409
436
 
437
+ // Parse `data` using the `schema`
410
438
  const results = parseData(data, schema)
411
439
 
440
+ // There's one data row in the `.xlsx` file.
412
441
  results.length === 1
413
442
 
414
- // `errors` items have shape: `{ column, error, reason?, value?, type? }`.
443
+ // There have been no errors when parsing the first data row, so `errors` is `undefined`.
444
+ // Should there have been any errors when parsing the row, `errors` would've been an array
445
+ // with items having shape: `{ column, error, reason?, value?, type? }`.
415
446
  results[0].errors === undefined
416
447
 
417
448
  results[0].object === {
418
- date: new Date(2018, 3 - 1, 24),
419
- numberOfStudents: 10,
420
- course: {
421
- isFree: true,
422
- title: 'Chemistry'
423
- },
449
+ startDate: new Date(Date.UTC(2018, 3 - 1, 24)),
450
+ seats: 10,
451
+ status: 'SCHEDULED',
424
452
  contact: '+11234567890',
425
- status: 'SCHEDULED'
453
+ course: {
454
+ title: 'Basic Algebra',
455
+ categories: ['Math', 'Arithmetic']
456
+ isFree: true
457
+ }
458
+ }
459
+
460
+ // An example of a custom `type` parser function.
461
+ // It will parse the cell value when it's not empty.
462
+ function PhoneNumber(value) {
463
+ const number = parsePhoneNumber(value)
464
+ if (!number) {
465
+ throw new Error('invalid')
466
+ }
467
+ return number
426
468
  }
427
469
  ```
428
470
 
429
- <!-- #### Schema: Tips and Features -->
471
+ An example of how an application could handle the `results`:
430
472
 
431
- <!-- If no `type` is specified then the cell value is returned "as is": as a string, number, date or boolean. -->
473
+ ```js
474
+ const errors = []
475
+ const objects = []
476
+
477
+ // If this code was written in TypeScript, `errors` and `objects` would've been declared as:
478
+ // const errors: { error: ParseDataError, row: number }[] = []
479
+ // const objects: Object[] = []
480
+
481
+ let row = 1
482
+ for (const { errors: errorsInRow, object } of results) {
483
+ if (errorsInRow) {
484
+ for (const error of errorsInRow) {
485
+ errors.push({ error, row })
486
+ }
487
+ } else {
488
+ objects.push(object)
489
+ }
490
+ row++
491
+ }
432
492
 
433
- <!-- There are also some additional exported `type`s available: -->
493
+ if (errors.length > 0) {
494
+ for (const { error, row } of errors) {
495
+ console.error('Error in data row', row, 'column', error.column, ':', error.error, error.reason || '')
496
+ }
497
+ } else {
498
+ console.log('Objects', objects)
499
+ }
500
+ ```
434
501
 
435
502
  <details>
436
- <summary>An example of a <strong>custom <code>type</code></strong></summary>
503
+ <summary>An example of defining a <strong>custom <code>type</code></strong> in <strong>TypeScript</strong></summary>
437
504
 
438
505
  #####
439
506
 
440
- Here's an example of a basic custom `type`. It calls a custom `parseValue()` function to parse a cell value, and produces an `"invalid"` error if the value couldn't be parsed. If a cell is empty, it will not be parsed.
507
+ ```ts
508
+ import type {
509
+ Schema,
510
+ CellValue,
511
+ ParseDataError,
512
+ ParseDataCustomType,
513
+ ParseDataCustomTypeErrorMessage
514
+ } from 'read-excel-file/node'
441
515
 
442
- ```js
443
- {
444
- property: {
445
- column: 'COLUMN TITLE',
446
- type: (value) => {
447
- try {
448
- return parseValue(value)
449
- } catch (error) {
450
- console.error(error)
451
- throw new Error('invalid')
452
- }
516
+ type ColumnTitle = 'COLUMN TITLE 1' | 'COLUMN TITLE 2'
517
+
518
+ type CustomTypeValue = string
519
+
520
+ function CustomType(value: CellValue): CustomTypeValue {
521
+ if (typeof value !== 'string') {
522
+ throw new Error('not_a_string')
523
+ }
524
+ return '~' + value + '~'
525
+ }
526
+
527
+ type CustomTypeErrorMessage<Type extends ParseDataCustomType<unknown>> =
528
+ Type extends typeof CustomType
529
+ ? 'not_a_string'
530
+ : never
531
+
532
+ // type CustomTypeErrorReason<
533
+ // Type extends ParseDataCustomType<unknown>,
534
+ // ErrorMessage extends ParseDataCustomTypeErrorMessage<Type>
535
+ // > =
536
+ // Type extends typeof CustomType
537
+ // ? (ErrorMessage extends 'not_a_string' ? undefined : never)
538
+ // : never
539
+
540
+ type PossibleError = ParseDataError<
541
+ ColumnTitle,
542
+ typeof CustomType,
543
+ CustomTypeErrorMessage<typeof CustomType>
544
+ // CustomTypeErrorReason<typeof CustomType, CustomTypeErrorMessage<typeof CustomType>>
545
+ >
546
+
547
+ interface Object {
548
+ property1: CustomTypeValue;
549
+ property2?: string;
550
+ }
551
+
552
+ const schema: Schema<Object, ColumnTitle> = {
553
+ property1: {
554
+ column: 'COLUMN TITLE 1',
555
+ type: CustomType,
556
+ required: true
557
+ },
558
+ property2: {
559
+ column: 'COLUMN TITLE 2',
560
+ type: String
561
+ }
562
+ }
563
+
564
+ const results = parseData<Object, ColumnTitle, PossibleError>([
565
+ ['COLUMN TITLE 1', 'COLUMN TITLE 2'],
566
+ ['Value 1', 'Value 2']
567
+ ], schema)
568
+
569
+ const errors: {
570
+ error: PossibleError,
571
+ row: number
572
+ }[] = []
573
+
574
+ const objects: Object[] = []
575
+
576
+ let row = 1
577
+ for (const { errors: errorsInRow, object } of results) {
578
+ if (errorsInRow) {
579
+ for (const error of errorsInRow) {
580
+ errors.push({ error, row })
453
581
  }
582
+ } else {
583
+ objects.push(object)
584
+ }
585
+ row++
586
+ }
587
+
588
+ if (errors.length > 0) {
589
+ for (const { error, row } of errors) {
590
+ console.error('Error in data row', row, 'column', error.column, ':', error.error, error.reason || '')
454
591
  }
592
+ } else {
593
+ console.log('Objects', objects)
455
594
  }
456
595
  ```
457
596
  </details>
@@ -18,14 +18,24 @@ import {
18
18
  Schema
19
19
  } from '../types/parseData/parseDataSchema.d.js';
20
20
 
21
+ import {
22
+ ParseDataError
23
+ } from '../types/parseData/parseDataError.d.js';
24
+
21
25
  export {
22
26
  CellValue,
23
27
  Row,
24
- SheetData
28
+ SheetData,
29
+ Sheet
25
30
  } from '../types/types.d.js';
26
31
 
27
32
  export {
28
- ParseDataValueCustomType as ParseDataValueType,
33
+ ParseDataCustomType,
34
+ // Base `type`s when parsing data.
35
+ StringType as String,
36
+ DateType as Date,
37
+ NumberType as Number,
38
+ BooleanType as Boolean,
29
39
  // Additional built-in `type`s when parsing data.
30
40
  Integer,
31
41
  Email,
@@ -33,6 +43,8 @@ export {
33
43
  } from '../types/parseData/parseDataValueType.d.js';
34
44
 
35
45
  export {
46
+ ParseDataCustomTypeErrorMessage,
47
+ ParseDataCustomTypeErrorReason,
36
48
  ParseDataError,
37
49
  ParseDataValueRequiredError
38
50
  } from '../types/parseData/parseDataError.d.js';
@@ -63,9 +75,10 @@ export function readSheet<ParsedNumber = number>(
63
75
 
64
76
  export function parseData<
65
77
  Object extends object,
66
- ColumnTitle extends string
78
+ ColumnTitle extends string,
79
+ Error extends ParseDataError
67
80
  >(
68
81
  data: SheetData,
69
82
  schema: Schema<Object, ColumnTitle>,
70
83
  options?: ParseDataOptions
71
- ): ParseDataResult<Object>;
84
+ ): ParseDataResult<Object, Error>;