UNPKG

read-excel-file

Version:

Read `.xlsx` files in a web browser or in Node.js

738 lines (706 loc) 33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = parseSheetData; exports.getNextSubstring = getNextSubstring; exports.parseSeparatedSubstrings = parseSeparatedSubstrings; exports.parseSheetDataWithPerRowErrors = parseSheetDataWithPerRowErrors; exports.parseValue = parseValue; var _Number = _interopRequireDefault(require("./types/Number.js")); var _String = _interopRequireDefault(require("./types/String.js")); var _Boolean = _interopRequireDefault(require("./types/Boolean.js")); var _Date = _interopRequireDefault(require("./types/Date.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } /** * Converts spreadsheet-alike data structure into an array of JSON objects. * * Parameters: * * * `data` — An array of rows, each row being an array of cells. The first row should be the list of column headers and the rest of the rows should be the data. * * `schema` — A "to JSON" convertion schema (see above). * * `options` — (optional) Schema conversion parameters of `read-excel-file`: * * `propertyValueWhenColumnIsMissing` — By default, when some of the `schema` columns are missing in the input `data`, those properties are set to `undefined` in the output objects. Pass `propertyValueWhenColumnIsMissing: null` to set such "missing column" properties to `null` in the output objects. * * `propertyValueWhenCellIsEmpty` — By default, when it encounters a `null` value in a cell in input `data`, it sets it to `undefined` in the output object. Pass `propertyValueWhenCellIsEmpty: null` to make it set such values as `null`s in output objects. * // * `shouldSkipRequiredValidationWhenColumnIsMissing: (column: string, { object }) => boolean` — By default, it does apply `required` validation to `schema` properties for which columns are missing in the input `data`. One could pass a custom `shouldSkipRequiredValidationWhenColumnIsMissing(column, { object })` to disable `required` validation for missing columns in some or all cases. * * `transformEmptyObject(object, { path? })` — By default, it returns `null` for "empty" objects. One could override that value using `transformEmptyObject(object, { path })` parameter. The value applies to both top-level object and any nested sub-objects in case of a nested schema, hence the additional (optional) `path?: string` parameter. * * `transformEmptyArray(array, { path })` — By default, it returns `null` for an "empty" array value. One could override that value using `transformEmptyArray(array, { path })` parameter. * * `separatorCharacter` — By default, it splits array-type cell values by a comma character. * * When parsing a property value, in case of an error, the value of that property is gonna be `undefined`. * * @param {SheetData} data - An array of rows, each row being an array of cells. * @param {object} schema * @param {object} [options] * @param {any} [options.propertyValueWhenColumnIsMissing] — By default, when some of the `schema` columns are missing in the input `data`, those properties are set to `undefined` in the output objects. Pass `propertyValueWhenColumnIsMissing: null` to set such "missing column" properties to `null` in the output objects. * @param {any} [options.propertyValueWhenCellIsEmpty] — By default, when it encounters a `null` value in a cell in input `data`, it leaves the value as is. Pass a custom `propertyValueWhenCellIsEmpty` to make it set such values to that value. * // @param {boolean} [options.shouldSkipRequiredValidationWhenColumnIsMissing(column: string, { object })] — By default, it does apply `required` validation to `schema` properties for which columns are missing in the input `data`. One could pass a custom `shouldSkipRequiredValidationWhenColumnIsMissing(column, { object })` to disable `required` validation for missing columns in some or all cases. * @param {function} [options.transformEmptyObject(object, { path })] — By default, it returns `null` for an "empty" resulting object. One could override that value using `transformEmptyObject(object, { path })` parameter. The value applies to both top-level object and any nested sub-objects in case of a nested schema, hence the additional `path?: string` parameter. * @param {function} [options.transformEmptyArray(array, { path })] — By default, it returns `null` for an "empty" array value. One could override that value using `transformEmptyArray(array, { path })` parameter. * @param {string} [options.separatorCharacter] — When specified, string values will be split by this separator to get the array. * @return {object} — An object of shape `{ objects, errors }`. Either `objects` or `errors` is going to be `undefined`. */ function parseSheetData(data, schema, optionsCustom) { var objects = []; var errors = []; var parsedRows = parseSheetDataWithPerRowErrors(data, schema, optionsCustom); var parsedRowIndex = 0; for (var _iterator = _createForOfIteratorHelperLoose(parsedRows), _step; !(_step = _iterator()).done;) { var _step$value = _step.value, object = _step$value.object, rowErrors = _step$value.errors; if (rowErrors) { errors = errors.concat(rowErrors.map( // Add row number property to each row error. function (rowError) { return _objectSpread(_objectSpread({}, rowError), {}, { row: parsedRowIndex + 1 }); })); } else { objects.push(object); } parsedRowIndex++; } if (errors.length > 0) { return { errors: errors }; } return { objects: objects }; } // This one is only used in tests. function parseSheetDataWithPerRowErrors(data, schema, optionsCustom) { validateSchema(schema); var options = applyDefaultOptions(optionsCustom); var _data = _toArray(data), columns = _data[0], dataRows = _data.slice(1); return dataRows.map(function (row) { return parseDataRow(row, schema, columns, options); }); } function parseDataRow(dataRow, schema, columns, options) { // Create a `schemaEntry` for the top-level object. var schemaEntry = { schema: schema }; // Parse the values in the given data row into an object. var _parseProperty = parseProperty(dataRow, schemaEntry, undefined, columns, options), value = _parseProperty.value, isEmptyValue = _parseProperty.isEmptyValue, errors = _parseProperty.errors, children = _parseProperty.children; // Simulate a "dummy" parent object for the top-level object. // It will be used when running `required` validations. var dummyParentObject = { // The "dummy" parent object has a "dummy" value. // This value is irrelevant because it won't be read anywhere. value: PARSED_OBJECT_TREE_START, // The "dummy" parent object is empty if the parsed row is empty. isEmptyValue: isEmptyValue, // The "dummy" object has the same errors as the parsed row. errors: errors, // The parsed object by default is not required to have any data // so the "dummy" object is not required. isRequired: undefined }; // Run any `required` validations. // // `required` validations should be run after the entire data row has been parsed, // i.e. when the entire object structure has been parsed. // The reason is that a `required` validation could be either a simple boolean or a "complex" function. // In the latter case, the result of a `required()` function may depend on any other property of the object, // hence the actual `required` flag value could only be obtained after the entire data row has been parsed. // // For example, consider a top-level object: // // { // firstName: string, // lastName: string, // pet?: { name: string } // } // // A corresponding schema would be: // // { // firstName: { // required: true // }, // lastName: { // required: true // }, // pet: { // required: false, // schema: { // name: { // required: true // } // } // } // } // // I.e. when a `pet` exists, it must have a `name`. // // In such case, the `required: true` check of the `pet`'s `name` property // should not be performed if the `pet` is not present, because the `pet` nested object // is marked as `required: false`, meaning that `pet` data is not required to be present. // var requiredErrors = runPendingRequiredValidations(schemaEntry, value, isEmptyValue, errors, children, // Simulate a "dummy" parent object for the top-level object. dummyParentObject.isRequired, dummyParentObject.value, dummyParentObject.isEmptyValue, dummyParentObject.errors, columns); // If there were any errors, whether caused by `required` // or occured while parsing the values, return those errors. if (errors || requiredErrors) { return { errors: (errors || []).concat(requiredErrors || []) }; } // Return the parsed object. return { object: transformValue(value, isEmptyValue, undefined, options) }; } function parseObject(row, schema, path, columns, options) { var object = {}; var isEmptyObject = true; var errors = []; var children = []; // For each property of the object. for (var _i = 0, _Object$keys = Object.keys(schema); _i < _Object$keys.length; _i++) { var key = _Object$keys[_i]; var child = parseProperty(row, schema[key], getPropertyPath(key, path), columns, options); if (child.errors) { errors = errors.concat(child.errors); } else { object[key] = transformValue(child.value, child.isEmptyValue, getPropertyPath(key, path), options); // Potentially unmark the object as "empty". if (isEmptyObject && !child.isEmptyValue) { isEmptyObject = false; } } children.push(_objectSpread(_objectSpread({}, child), {}, { // `schemaEntry` will be used when running `required` validation of this property (later), schemaEntry: schema[key] })); } // If there were any errors, return them. if (errors.length > 0) { return { // Return the errors. errors: errors, // Return the `children` because `required` validations still have to be run (later). children: children }; } return { value: object, isEmptyValue: isEmptyObject, // Return the `children` because `required` validations still have to be run (later). children: children }; } function parseProperty(row, schemaEntry, path, columns, options) { var columnIndex = schemaEntry.column ? columns.indexOf(schemaEntry.column) : undefined; var isMissingColumn = schemaEntry.column ? columnIndex < 0 : undefined; var _ref = schemaEntry.column ? isMissingColumn ? { value: options.propertyValueWhenColumnIsMissing, isEmptyValue: true } : parseCellValueWithPossibleErrors(row[columnIndex], schemaEntry, columnIndex, options) : parseObject(row, schemaEntry.schema, path, columns, options), value = _ref.value, isEmptyValue = _ref.isEmptyValue, errors = _ref.errors, children = _ref.children; // If there were any errors, return them. if (errors) { return { // Return the errors. errors: errors, // Return the `children` because `required` validations still have to be run (later). children: children }; } return { value: value, isEmptyValue: isEmptyValue, // Return the `children` because `required` validations still have to be run (later). children: children }; } function parseCellValueWithPossibleErrors(cellValue, schemaEntry, columnIndex, options) { var _parseCellValue = parseCellValue(cellValue, schemaEntry, options), value = _parseCellValue.value, isEmptyValue = _parseCellValue.isEmptyValue, errorMessage = _parseCellValue.error, errorReason = _parseCellValue.reason; if (errorMessage) { var error = createError({ error: errorMessage, reason: errorReason, column: schemaEntry.column, columnIndex: columnIndex, valueType: schemaEntry.type, value: cellValue }); return { errors: [error] }; } return { value: value, isEmptyValue: isEmptyValue }; } /** * Converts a cell value value to a javascript typed value. * @param {any} cellValue * @param {object} schemaEntry * @param {string} propertyPath * @param {object} options * @return {{ value?: any, isEmptyValue: boolean } | { error: string, reason?: string }} */ function parseCellValue(cellValue, schemaEntry, options) { if (cellValue === undefined) { // This isn't supposed to be possible when reading spreadsheet data: // cell values are always read as `null` when those cells are empty. // It's currently impossible for `read-excel-file` to return `undefined` cell value. // Here it uses some "sensible default" fallback by treating `undefined` as "column missing". return { value: options.propertyValueWhenColumnIsMissing, isEmptyValue: true }; } if (cellValue === null) { return { value: options.propertyValueWhenCellIsEmpty, isEmptyValue: true }; } // Parse comma-separated cell value. if (Array.isArray(schemaEntry.type)) { return parseArrayValue(cellValue, schemaEntry, options); } return parseValue(cellValue, schemaEntry, options); } /** * Converts textual value to a javascript typed array value. * @param {any} value * @param {object} schemaEntry * @param {object} options * @return {{ value?: any, isEmptyValue: boolean } | { error: string, reason?: string }} */ function parseArrayValue(value, schemaEntry, options) { // If the cell value is not a string — i.e. a number, a boolean, a Date — // then throw an error. if (typeof value !== 'string') { return { error: 'not_a_string' }; } var isEmptyArray = true; var errors = []; var reasons = []; var values = parseSeparatedSubstrings(value, options.separatorCharacter).map(function (substring) { // If any substring was already detected to be invalid // don't attempt to parse any other substrings. if (errors.length > 0) { return; } // If an empty substring was extracted, it means that there was an out-of-place separator. if (!substring) { errors.push('invalid'); reasons.push('syntax'); return; } var _parseValue = parseValue(substring, schemaEntry, options), value = _parseValue.value, isEmptyValue = _parseValue.isEmptyValue, error = _parseValue.error, reason = _parseValue.reason; if (error) { errors.push(error); reasons.push(reason); return; } if (isEmptyArray && !isEmptyValue) { isEmptyArray = false; } return value; }); if (errors.length > 0) { return { error: errors[0], reason: reasons[0] }; } return { value: values, isEmptyValue: isEmptyArray }; } /** * Converts textual value to a javascript typed value. * @param {any} value * @param {object} schemaEntry * @param {object} options * @return {{ value?: any, isEmptyValue: boolean } | { error: string }} */ function parseValue(value, schemaEntry, options) { // `null` values (i.e. empty cells) don't get parsed. if (value === null) { return { value: null, isEmptyValue: true }; } // Parse the value according to the `type` that is specified in the schema entry. var result; if (schemaEntry.type) { result = parseValueOfType(value, // Get the type of the value. // // Handle the case if it's a comma-separated value. // Example `type`: String[] // Example Input Value: 'Barack Obama, "String, with, colons", Donald Trump' // Example Parsed Value: ['Barack Obama', 'String, with, colons', 'Donald Trump'] // Array.isArray(schemaEntry.type) ? schemaEntry.type[0] : schemaEntry.type, options); } else { // If the `type` is not specified for a given schema entry, the default one is `String`. result = { value: value }; // throw new Error('Invalid schema entry: no `type` specified:\n\n' + JSON.stringify(schemaEntry, null, 2)) } // If there was an error when parsing the value then return the error. if (result.error) { return result; } // If the parsed value is empty, return it. if (value === null) { return { value: null, isEmptyValue: true }; } // Value is not empty. // Validate it and return. // Perform `oneOf` validation. if (schemaEntry.oneOf) { var errorAndReason = validateOneOf(result.value, schemaEntry.oneOf); if (errorAndReason) { return errorAndReason; } } // Perform `validate()` validation. if (schemaEntry.validate) { try { schemaEntry.validate(result.value); } catch (error) { return { error: error.message }; } } // Return the value. return { value: result.value, isEmptyValue: isEmptyValue(result.value) }; } function validateOneOf(value, oneOf) { if (oneOf.indexOf(value) < 0) { return { error: 'invalid', reason: 'unknown' }; } } /** * Converts cell value to a javascript typed value. * @param {(string|number|boolean|Date)} value * @param {function} type * @return {object} Either `{ value: (string|number|Date|boolean) }` or `{ error: string, reason?: string }` */ function parseValueOfType(value, type) { switch (type) { case String: return parseValueUsingTypeParser(value, _String["default"]); case Number: return parseValueUsingTypeParser(value, _Number["default"]); case Date: return parseValueUsingTypeParser(value, _Date["default"]); case Boolean: return parseValueUsingTypeParser(value, _Boolean["default"]); default: // Validate `type` if (typeof type !== 'function') { throw new Error("Unsupported schema `type`: ".concat(type && type.name || type)); } return parseValueUsingTypeParser(value, type); } } /** * Converts textual value to a custom value using supplied `type`. * @param {any} value * @param {function} type * @return {{ value: any, error: string }} */ function parseValueUsingTypeParser(value, type) { try { var parsedValue = type(value); // Returning `undefined` from a `type` parser is treated as returning `null`. if (parsedValue === undefined) { return { value: null }; } return { value: parsedValue }; } catch (error) { var result = { error: error.message }; // Built-in types such as `Number` or `Date` may also report // a specific `reason` of the error. if (error.reason) { result.reason = error.reason; } return result; } } // Extracts a substring from a string. function getNextSubstring(string, separatorCharacter, startIndex) { var i = 0; var substring = ''; while (startIndex + i < string.length) { var character = string[startIndex + i]; if (character === separatorCharacter) { return [substring, i]; } // Previously, it used to treat `"` character similar to how it's treated in `.csv` files: // any commas inside quotes are ignored. But then I thought that it could introduce more // issues than it was originally intending to fix, and it also didn't provide an "escape" mechanism. // Overall, a decision was made to simplify the whole thing and drop the concept of quotes as special characters. // // else if (character === '"') { // const quotedSubstring = getNextSubstring(string, '"', startIndex + i + 1) // substring += quotedSubstring[0] // i += '"'.length + quotedSubstring[1] + '"'.length // } else { substring += character; i++; } } return [substring, i]; } /** * Parses a string of comma-separated substrings into an array of substrings. * (the `export` is just for tests) * @param {string} string — A string of comma-separated substrings. * @return {string[]} An array of substrings. */ function parseSeparatedSubstrings(string, separatorCharacter) { var elements = []; var index = 0; while (index < string.length) { var _getNextSubstring = getNextSubstring(string, separatorCharacter, index), _getNextSubstring2 = _slicedToArray(_getNextSubstring, 2), substring = _getNextSubstring2[0], length = _getNextSubstring2[1]; index += length + separatorCharacter.length; elements.push(substring.trim()); } return elements; } function transformValue(value, isEmptyValue, path, options) { if (isEmptyValue) { if (isObject(value)) { return options.transformEmptyObject(value, { path: path }); } else if (Array.isArray(value)) { return options.transformEmptyArray(value, { path: path }); } } return value; } function getPropertyPath(propertyName, parentObjectPath) { return "".concat(parentObjectPath ? parentObjectPath + '.' : '').concat(propertyName); } // Recursively runs `required` validations for the parsed data row tree. function runPendingRequiredValidations(schemaEntry, value, isEmptyValue, errors, children, parentObjectIsRequired, parentObjectValue, parentObjectValueIsEmpty, parentObjectErrors, columns) { var requiredErrors = []; // See if this property is required. var isRequired = isPropertyRequired(schemaEntry, parentObjectIsRequired, parentObjectValue, parentObjectValueIsEmpty, parentObjectErrors); // If this property is required and empty, create a "required" error. if (isRequired && isEmptyValue) { requiredErrors.push(createError({ error: 'required', column: schemaEntry.column, columnIndex: columns.indexOf(schemaEntry.column), valueType: schemaEntry.type, value: value })); } // Run `required` validations of the children. if (children) { for (var _iterator2 = _createForOfIteratorHelperLoose(children), _step2; !(_step2 = _iterator2()).done;) { var child = _step2.value; var requiredErrorsOfChild = runPendingRequiredValidations(child.schemaEntry, child.value, child.isEmptyValue, child.errors, child.children, // The following properties describe the parent object of the `child`, // i.e. the current (iterated) object. isRequired, value, isEmptyValue, errors, columns); if (requiredErrorsOfChild) { requiredErrors = requiredErrors.concat(requiredErrorsOfChild); } } } if (requiredErrors.length > 0) { return requiredErrors; } } function isPropertyRequired(schemaEntry, parentObjectIsRequired, parentObjectValue, parentObjectValueIsEmpty, parentObjectErrors) { // If the parent object is marked as `required: false` then it's allowed // to be absent entirely from the input data. If that's the case, // i.e. if the parent object is absent entirely from the input data, // then any descendant properties of such object are allowed to be absent too, // which means that they should also be considered being `required: false`. // // Also, if the parent object couldn't be parsed due to some non-`required` errors, // it can't be known whether it's actually empty or not. In case of such uncertainty, // the code shouldn't attempt to be overly smart and do things that might not be necessary, // so such parent object is just assumed to be empty in order to not falsly trigger // any `required` validations that otherwise wouldn't have been run. // In other words, skipping some `required` validations is better than // running `required` validations that shouldn't have been run. // if (parentObjectIsRequired === false && (parentObjectValueIsEmpty || parentObjectErrors)) { return false; } return schemaEntry.required && (typeof schemaEntry.required === 'boolean' ? schemaEntry.required : // If there were any non-`required` errors when parsing the parent object, // the `parentObject` will be `undefined`. In that case, "complex" `required()` // validations — the ones where `required` is a function — can't really be run // because those validations assume a fully and correctly parsed parent object // be passed as an argument, and the thing is that the `parentObject` is unknown. // As a result, only "basic" `required` validations could be run, // i.e. the ones where `required` is just a boolean, and "complex" `required` // validations, i.e. the ones where `required` is a functions, should be skipped, // because it's better to skip some `required` errors than to trigger falsy ones. parentObjectErrors ? false : schemaEntry.required(parentObjectValue)); } function createError(_ref2) { var column = _ref2.column, columnIndex = _ref2.columnIndex, valueType = _ref2.valueType, value = _ref2.value, errorMessage = _ref2.error, reason = _ref2.reason; var error = { error: errorMessage, column: column, columnIndex: columnIndex, value: value }; if (reason) { error.reason = reason; } // * Regular values specify a `type?` property, which is included in the `error` object. // * Nested objects specify a `schema` property, which is not included in the `error` object. if (valueType) { error.type = valueType; } return error; } function validateSchema(schema) { for (var _i2 = 0, _Object$keys2 = Object.keys(schema); _i2 < _Object$keys2.length; _i2++) { var key = _Object$keys2[_i2]; var schemaEntry = schema[key]; // Validate that the `schema` is not using a deprecated `type: nestedSchema` format. if (_typeof(schemaEntry.type) === 'object' && !Array.isArray(schemaEntry.type)) { throw new Error('When defining a nested schema, use a `schema` property instead of a `type` property'); } // Validate that every property has a source `column` title specified for it. if (!schemaEntry.schema) { if (!schemaEntry.column) { throw new Error("\"column\" not defined for schema entry \"".concat(key, "\".")); } } } // A nested object could have a `required` property but the only allowed value is `false`. // The reason why `true` value is not allowed is because in case of a "required" error // there's no single column title corresponding to such nested object, and column title // is required to create a "required" error. validateObjectSchemaRequiredProperty(schema, undefined); } function validateObjectSchemaRequiredProperty(schema, required) { if (required !== undefined && required !== false) { throw new Error("In a schema, a nested object can have a `required` property but the only allowed value is `undefined` or `false`. Otherwise, a \"required\" error for a nested object would have to include a specific `column` title and a nested object doesn't have one. You've specified the following `required`: ".concat(required)); } // For each property of the described object. for (var _i3 = 0, _Object$keys3 = Object.keys(schema); _i3 < _Object$keys3.length; _i3++) { var key = _Object$keys3[_i3]; // If this property is itself an object. if (isObject(schema[key].schema)) { // Validate that a `column` property can't coexist with a `schema` property. if (schema[key].column) { throw new Error("In a schema, `column` property is only allowed when describing a property value rather than a nested object. Key: ".concat(key, ". Schema:\n").concat(JSON.stringify(schema[key], null, 2))); } // Recurse into the child object. validateObjectSchemaRequiredProperty(schema[key].schema, schema[key].required); } } } function isEmptyValue(value) { return value === undefined || value === null; } var DEFAULT_OPTIONS = { propertyValueWhenColumnIsMissing: undefined, propertyValueWhenCellIsEmpty: null, // shouldSkipRequiredValidationWhenColumnIsMissing: () => false, // `transformEmptyObject(object, { path })` applies to both the top-level object // and any of its nested objects. transformEmptyObject: function transformEmptyObject() { return null; }, transformEmptyArray: function transformEmptyArray() { return null; }, separatorCharacter: ',' }; function applyDefaultOptions(options) { if (options) { return _objectSpread(_objectSpread({}, DEFAULT_OPTIONS), options); } else { return DEFAULT_OPTIONS; } } // This `value` marks the start of a tree structure that is parsed from a given data row. var PARSED_OBJECT_TREE_START = {}; var objectConstructor = {}.constructor; function isObject(object) { return object !== undefined && object !== null && object.constructor === objectConstructor; } //# sourceMappingURL=parseSheetData.js.map