parse-nested-form-data 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -62,6 +62,12 @@ should be installed as one of your project's `dependencies`:
62
62
  npm install parse-nested-form-data
63
63
  ```
64
64
 
65
+ and then import it in your project:
66
+
67
+ ```ts
68
+ import {parseFormData} from 'parse-nested-form-data'
69
+ ```
70
+
65
71
  ## Name
66
72
 
67
73
  The name prop is used to tell the parser how to create an object out of the
@@ -339,7 +345,7 @@ where most of the setup is from.
339
345
  <!-- prettier-ignore-start -->
340
346
  [npm]: https://www.npmjs.com
341
347
  [node]: https://nodejs.org
342
- [build-badge]: https://img.shields.io/github/workflow/status/milamer/parse-nested-form-data/validate?logo=github&style=flat-square
348
+ [build-badge]: https://img.shields.io/github/actions/workflow/status/milamer/parse-nested-form-data/validate.yml?branch=main&logo=github&style=flat-square
343
349
  [build]: https://github.com/milamer/parse-nested-form-data/actions?query=workflow%3Avalidate
344
350
  [coverage-badge]: https://img.shields.io/codecov/c/github/milamer/parse-nested-form-data.svg?style=flat-square
345
351
  [coverage]: https://codecov.io/github/milamer/parse-nested-form-data
package/dist/index.d.ts CHANGED
@@ -64,6 +64,24 @@ export declare class MixedArrayError extends Error {
64
64
  key: string;
65
65
  constructor(key: string);
66
66
  }
67
+ /**
68
+ * Thrown when a path part would access or assign a property on
69
+ * `Object.prototype` (`__proto__`, `constructor`, `prototype`). Rejected
70
+ * regardless of whether pollution would actually occur, to keep input
71
+ * unambiguous.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const formData = new FormData()
76
+ * formData.append('__proto__.polluted', 'yes')
77
+ * parseFormData(formData)
78
+ * // throws ForbiddenKeyError('__proto__')
79
+ * ```
80
+ */
81
+ export declare class ForbiddenKeyError extends Error {
82
+ key: string;
83
+ constructor(key: string);
84
+ }
67
85
  type JsonObject = {
68
86
  [Key in string]?: JsonValue;
69
87
  };
@@ -77,6 +77,28 @@ class MixedArrayError extends Error {
77
77
  this.key = key;
78
78
  }
79
79
  }
80
+ /**
81
+ * Thrown when a path part would access or assign a property on
82
+ * `Object.prototype` (`__proto__`, `constructor`, `prototype`). Rejected
83
+ * regardless of whether pollution would actually occur, to keep input
84
+ * unambiguous.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const formData = new FormData()
89
+ * formData.append('__proto__.polluted', 'yes')
90
+ * parseFormData(formData)
91
+ * // throws ForbiddenKeyError('__proto__')
92
+ * ```
93
+ */
94
+ class ForbiddenKeyError extends Error {
95
+ constructor(key) {
96
+ super(`Forbidden key at path part ${key}`);
97
+ this.key = void 0;
98
+ this.key = key;
99
+ }
100
+ }
101
+ const FORBIDDEN_OBJECT_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
80
102
  function isJsonObject(val) {
81
103
  return typeof val === 'object' && !Array.isArray(val) && val !== null && !(val instanceof webFile.File);
82
104
  }
@@ -147,6 +169,59 @@ function defaultTransform(entry) {
147
169
  value
148
170
  };
149
171
  }
172
+
173
+ /**
174
+ * Options to change the behavior of `parseFormData`.
175
+ * @param transformEntry - a function to transform the FormData entry into a path and a value
176
+ * (default: `defaultTransform`)
177
+ * @param removeEmptyString - skip empty values '' (default: `false`)
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const formData = new FormData()
182
+ * formData.append('a', '')
183
+ * formData.append('b', 'b')
184
+ * const result = parseFormData(formData, {removeEmptyString: true})
185
+ * // => {b: 'b'}
186
+ * ```
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const formData = new FormData()
191
+ * formData.append('a', 'a')
192
+ * formData.append('b', 'b')
193
+ * parseFormData(formData, {
194
+ * transformEntry: ([path, value], defaultTransform) => {
195
+ * return {
196
+ * path,
197
+ * value:
198
+ * typeof value === 'string'
199
+ * ? value.toUpperCase()
200
+ * : defaultTransform(value),
201
+ * }
202
+ * },
203
+ * })
204
+ * // => {a: 'A', b: 'B'}
205
+ * ```
206
+ */
207
+
208
+ /**
209
+ * A parsed part of a FormData path. It consists of four parts:
210
+ * @param path - the key to access the value in the intermediate result
211
+ * @param type - the type of the path part (array - e.g. [\d*], or object .e.g. .key)
212
+ * @param default - the default value of the next path part ([] for array, {} for object)
213
+ * @param pathToPart - the full path to the current path part
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * const path = 'a[0].b'
218
+ * const result = extractPathParts(path)
219
+ * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},
220
+ * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},
221
+ * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]
222
+ * ```
223
+ */
224
+
150
225
  /**
151
226
  *
152
227
  * Transforms a FormData path into an array of `PathPart`s.
@@ -236,6 +311,9 @@ function extractPathParts(path) {
236
311
  *
237
312
  */
238
313
  function handlePathPart(pathPart, currentPathObject, arraysWithOrder) {
314
+ if (FORBIDDEN_OBJECT_KEYS.has(pathPart.path)) {
315
+ throw new ForbiddenKeyError(pathPart.pathToPart);
316
+ }
239
317
  if (pathPart.type === 'object') {
240
318
  if (Array.isArray(currentPathObject)) {
241
319
  throw new DuplicateKeyError(pathPart.pathToPart);
@@ -346,11 +424,10 @@ function handlePathPart(pathPart, currentPathObject, arraysWithOrder) {
346
424
  * - e.g. `a[0]` and `a[]`
347
425
  * - e.g. `a[]` and `a[0]`
348
426
  */
349
- function parseFormData(formData, _temp) {
350
- let {
351
- removeEmptyString = false,
352
- transformEntry = defaultTransform
353
- } = _temp === void 0 ? {} : _temp;
427
+ function parseFormData(formData, {
428
+ removeEmptyString = false,
429
+ transformEntry = defaultTransform
430
+ } = {}) {
354
431
  const result = {};
355
432
 
356
433
  // all arrays we need to squash (in place) later
@@ -389,5 +466,6 @@ function parseFormData(formData, _temp) {
389
466
  }
390
467
 
391
468
  exports.DuplicateKeyError = DuplicateKeyError;
469
+ exports.ForbiddenKeyError = ForbiddenKeyError;
392
470
  exports.MixedArrayError = MixedArrayError;
393
471
  exports.parseFormData = parseFormData;
@@ -73,6 +73,28 @@ class MixedArrayError extends Error {
73
73
  this.key = key;
74
74
  }
75
75
  }
76
+ /**
77
+ * Thrown when a path part would access or assign a property on
78
+ * `Object.prototype` (`__proto__`, `constructor`, `prototype`). Rejected
79
+ * regardless of whether pollution would actually occur, to keep input
80
+ * unambiguous.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const formData = new FormData()
85
+ * formData.append('__proto__.polluted', 'yes')
86
+ * parseFormData(formData)
87
+ * // throws ForbiddenKeyError('__proto__')
88
+ * ```
89
+ */
90
+ class ForbiddenKeyError extends Error {
91
+ constructor(key) {
92
+ super(`Forbidden key at path part ${key}`);
93
+ this.key = void 0;
94
+ this.key = key;
95
+ }
96
+ }
97
+ const FORBIDDEN_OBJECT_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
76
98
  function isJsonObject(val) {
77
99
  return typeof val === 'object' && !Array.isArray(val) && val !== null && !(val instanceof File);
78
100
  }
@@ -143,6 +165,59 @@ function defaultTransform(entry) {
143
165
  value
144
166
  };
145
167
  }
168
+
169
+ /**
170
+ * Options to change the behavior of `parseFormData`.
171
+ * @param transformEntry - a function to transform the FormData entry into a path and a value
172
+ * (default: `defaultTransform`)
173
+ * @param removeEmptyString - skip empty values '' (default: `false`)
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * const formData = new FormData()
178
+ * formData.append('a', '')
179
+ * formData.append('b', 'b')
180
+ * const result = parseFormData(formData, {removeEmptyString: true})
181
+ * // => {b: 'b'}
182
+ * ```
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * const formData = new FormData()
187
+ * formData.append('a', 'a')
188
+ * formData.append('b', 'b')
189
+ * parseFormData(formData, {
190
+ * transformEntry: ([path, value], defaultTransform) => {
191
+ * return {
192
+ * path,
193
+ * value:
194
+ * typeof value === 'string'
195
+ * ? value.toUpperCase()
196
+ * : defaultTransform(value),
197
+ * }
198
+ * },
199
+ * })
200
+ * // => {a: 'A', b: 'B'}
201
+ * ```
202
+ */
203
+
204
+ /**
205
+ * A parsed part of a FormData path. It consists of four parts:
206
+ * @param path - the key to access the value in the intermediate result
207
+ * @param type - the type of the path part (array - e.g. [\d*], or object .e.g. .key)
208
+ * @param default - the default value of the next path part ([] for array, {} for object)
209
+ * @param pathToPart - the full path to the current path part
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * const path = 'a[0].b'
214
+ * const result = extractPathParts(path)
215
+ * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},
216
+ * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},
217
+ * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]
218
+ * ```
219
+ */
220
+
146
221
  /**
147
222
  *
148
223
  * Transforms a FormData path into an array of `PathPart`s.
@@ -232,6 +307,9 @@ function extractPathParts(path) {
232
307
  *
233
308
  */
234
309
  function handlePathPart(pathPart, currentPathObject, arraysWithOrder) {
310
+ if (FORBIDDEN_OBJECT_KEYS.has(pathPart.path)) {
311
+ throw new ForbiddenKeyError(pathPart.pathToPart);
312
+ }
235
313
  if (pathPart.type === 'object') {
236
314
  if (Array.isArray(currentPathObject)) {
237
315
  throw new DuplicateKeyError(pathPart.pathToPart);
@@ -342,11 +420,10 @@ function handlePathPart(pathPart, currentPathObject, arraysWithOrder) {
342
420
  * - e.g. `a[0]` and `a[]`
343
421
  * - e.g. `a[]` and `a[0]`
344
422
  */
345
- function parseFormData(formData, _temp) {
346
- let {
347
- removeEmptyString = false,
348
- transformEntry = defaultTransform
349
- } = _temp === void 0 ? {} : _temp;
423
+ function parseFormData(formData, {
424
+ removeEmptyString = false,
425
+ transformEntry = defaultTransform
426
+ } = {}) {
350
427
  const result = {};
351
428
 
352
429
  // all arrays we need to squash (in place) later
@@ -384,4 +461,4 @@ function parseFormData(formData, _temp) {
384
461
  return result;
385
462
  }
386
463
 
387
- export { DuplicateKeyError, MixedArrayError, parseFormData };
464
+ export { DuplicateKeyError, ForbiddenKeyError, MixedArrayError, parseFormData };
@@ -77,6 +77,28 @@
77
77
  this.key = key;
78
78
  }
79
79
  }
80
+ /**
81
+ * Thrown when a path part would access or assign a property on
82
+ * `Object.prototype` (`__proto__`, `constructor`, `prototype`). Rejected
83
+ * regardless of whether pollution would actually occur, to keep input
84
+ * unambiguous.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const formData = new FormData()
89
+ * formData.append('__proto__.polluted', 'yes')
90
+ * parseFormData(formData)
91
+ * // throws ForbiddenKeyError('__proto__')
92
+ * ```
93
+ */
94
+ class ForbiddenKeyError extends Error {
95
+ constructor(key) {
96
+ super(`Forbidden key at path part ${key}`);
97
+ this.key = void 0;
98
+ this.key = key;
99
+ }
100
+ }
101
+ const FORBIDDEN_OBJECT_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
80
102
  function isJsonObject(val) {
81
103
  return typeof val === 'object' && !Array.isArray(val) && val !== null && !(val instanceof webFile.File);
82
104
  }
@@ -147,6 +169,59 @@
147
169
  value
148
170
  };
149
171
  }
172
+
173
+ /**
174
+ * Options to change the behavior of `parseFormData`.
175
+ * @param transformEntry - a function to transform the FormData entry into a path and a value
176
+ * (default: `defaultTransform`)
177
+ * @param removeEmptyString - skip empty values '' (default: `false`)
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const formData = new FormData()
182
+ * formData.append('a', '')
183
+ * formData.append('b', 'b')
184
+ * const result = parseFormData(formData, {removeEmptyString: true})
185
+ * // => {b: 'b'}
186
+ * ```
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const formData = new FormData()
191
+ * formData.append('a', 'a')
192
+ * formData.append('b', 'b')
193
+ * parseFormData(formData, {
194
+ * transformEntry: ([path, value], defaultTransform) => {
195
+ * return {
196
+ * path,
197
+ * value:
198
+ * typeof value === 'string'
199
+ * ? value.toUpperCase()
200
+ * : defaultTransform(value),
201
+ * }
202
+ * },
203
+ * })
204
+ * // => {a: 'A', b: 'B'}
205
+ * ```
206
+ */
207
+
208
+ /**
209
+ * A parsed part of a FormData path. It consists of four parts:
210
+ * @param path - the key to access the value in the intermediate result
211
+ * @param type - the type of the path part (array - e.g. [\d*], or object .e.g. .key)
212
+ * @param default - the default value of the next path part ([] for array, {} for object)
213
+ * @param pathToPart - the full path to the current path part
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * const path = 'a[0].b'
218
+ * const result = extractPathParts(path)
219
+ * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},
220
+ * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},
221
+ * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]
222
+ * ```
223
+ */
224
+
150
225
  /**
151
226
  *
152
227
  * Transforms a FormData path into an array of `PathPart`s.
@@ -236,6 +311,9 @@
236
311
  *
237
312
  */
238
313
  function handlePathPart(pathPart, currentPathObject, arraysWithOrder) {
314
+ if (FORBIDDEN_OBJECT_KEYS.has(pathPart.path)) {
315
+ throw new ForbiddenKeyError(pathPart.pathToPart);
316
+ }
239
317
  if (pathPart.type === 'object') {
240
318
  if (Array.isArray(currentPathObject)) {
241
319
  throw new DuplicateKeyError(pathPart.pathToPart);
@@ -346,11 +424,10 @@
346
424
  * - e.g. `a[0]` and `a[]`
347
425
  * - e.g. `a[]` and `a[0]`
348
426
  */
349
- function parseFormData(formData, _temp) {
350
- let {
351
- removeEmptyString = false,
352
- transformEntry = defaultTransform
353
- } = _temp === void 0 ? {} : _temp;
427
+ function parseFormData(formData, {
428
+ removeEmptyString = false,
429
+ transformEntry = defaultTransform
430
+ } = {}) {
354
431
  const result = {};
355
432
 
356
433
  // all arrays we need to squash (in place) later
@@ -389,6 +466,7 @@
389
466
  }
390
467
 
391
468
  exports.DuplicateKeyError = DuplicateKeyError;
469
+ exports.ForbiddenKeyError = ForbiddenKeyError;
392
470
  exports.MixedArrayError = MixedArrayError;
393
471
  exports.parseFormData = parseFormData;
394
472
 
@@ -1 +1 @@
1
- {"version":3,"file":"parse-nested-form-data.umd.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @name parse-nested-form-data\n * @license MIT license.\n * @copyright (c) 2022 Christian Schurr\n * @author Christian Schurr <chris@schurr.dev>\n */\n\nimport {File} from '@remix-run/web-file'\n\n/**\n * Thrown when a path is used multiple times or has missmatching path parts.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'b')\n * formData.append('a[0]', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a[0]')\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'b')\n * formData.append('a', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a')\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'b')\n * formData.append('a[]', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a[]')\n * ```\n *\n */\nexport class DuplicateKeyError extends Error {\n key: string\n constructor(key: string) {\n super(`Duplicate key at path part ${key}`)\n this.key = key\n }\n}\n/**\n * Thrown when an array is used at the same path with an order parameter and\n * without an order parameter.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'a')\n * formData.append('a[]', 'b')\n * parseFormData(formData)\n * // => throws `MixedArrayError(a[])`\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[]', 'a')\n * formData.append('a[0]', 'b')\n * parseFormData(formData)\n * // => throws `MixedArrayError(a[0])`\n * ```\n */\nexport class MixedArrayError extends Error {\n key: string\n constructor(key: string) {\n super(`Mixed array at path part ${key}`)\n this.key = key\n }\n}\n\ntype JsonObject = {[Key in string]?: JsonValue}\ntype JsonArray = Array<JsonValue>\ntype JsonValue =\n | string\n | number\n | JsonObject\n | JsonArray\n | boolean\n | null\n | File\ntype JsonLeafValue = Exclude<JsonValue, JsonArray | JsonObject>\n\nfunction isJsonObject(val: JsonValue): val is JsonObject {\n return (\n typeof val === 'object' &&\n !Array.isArray(val) &&\n val !== null &&\n !(val instanceof File)\n )\n}\n\n/**\n * Default Transformer for `parseFormData`.\n *\n * Transforms a FormData Entry into a path and a `JsonLeafValue`.\n *\n * - `path` starts with `+` -> transform value to `number`\n * - `path` starts with `&` -> transform value to `boolean`\n * - `path` starts with `-` -> transform value to `null`\n *\n * @example\n * ```ts\n * const entry = ['a[0]', 'b']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['+a[0]', '1']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: 1}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['&a[0]', 'true']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: true}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['-a[0]', 'null']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: null}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['a[0]', new File([''], 'file.txt')]\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: File}\n * ```\n *\n *\n * @param entry [path, value]: the FormData entry\n * @returns the path and the transformed value\n */\nfunction defaultTransform(entry: [path: string, value: string | File]): {\n path: string\n value: JsonLeafValue\n} {\n let path = entry[0]\n let value: JsonLeafValue = entry[1]\n if (path.startsWith('+')) {\n path = path.slice(1)\n value = Number(value)\n } else if (path.startsWith('&')) {\n path = path.slice(1)\n value = value === 'on' || value === 'true' || Boolean(Number(value))\n } else if (path.startsWith('-')) {\n path = path.slice(1)\n value = null\n }\n return {path, value}\n}\n\ntype DefaultTransform = typeof defaultTransform\n/**\n * Options to change the behavior of `parseFormData`.\n * @param transformEntry - a function to transform the FormData entry into a path and a value\n * (default: `defaultTransform`)\n * @param removeEmptyString - skip empty values '' (default: `false`)\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', '')\n * formData.append('b', 'b')\n * const result = parseFormData(formData, {removeEmptyString: true})\n * // => {b: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'a')\n * formData.append('b', 'b')\n * parseFormData(formData, {\n * transformEntry: ([path, value], defaultTransform) => {\n * return {\n * path,\n * value:\n * typeof value === 'string'\n * ? value.toUpperCase()\n * : defaultTransform(value),\n * }\n * },\n * })\n * // => {a: 'A', b: 'B'}\n * ```\n */\nexport type ParseFormDataOptions = {\n removeEmptyString?: boolean\n transformEntry?: (\n entry: [path: string, value: string | File],\n defaultTransform: DefaultTransform,\n ) => {path: string; value: JsonLeafValue}\n}\n\n/**\n * A parsed part of a FormData path. It consists of four parts:\n * @param path - the key to access the value in the intermediate result\n * @param type - the type of the path part (array - e.g. [\\d*], or object .e.g. .key)\n * @param default - the default value of the next path part ([] for array, {} for object)\n * @param pathToPart - the full path to the current path part\n *\n * @example\n * ```ts\n * const path = 'a[0].b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]\n * ```\n */\ntype PathPart = {\n path: string\n type: 'object' | 'array'\n default: {} | []\n pathToPart: string\n}\n/**\n *\n * Transforms a FormData path into an array of `PathPart`s.\n *\n * @param path - the path to extract the path parts from\n * @returns {Array<PathPart>} the extracted path parts\n *\n * @example\n * ```ts\n * const path = 'a[0].b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]\n * ```\n *\n * @example\n * ```ts\n * const path = 'a.b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: {}, pathToPart: 'a'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a.b'}]\n * ```\n *\n * @example\n * ```ts\n * const path = 'a[][0]'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '', type: 'array', default: [], pathToPart: 'a[]'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[][0]'}]\n * ```\n *\n *\n */\nfunction extractPathParts(path: string): Array<PathPart> {\n const re = /((?<array>\\d*)\\]|(?<pathPart>[^.[]+))(?<nextType>\\[|\\.|$)/g\n\n return Array.from(path.matchAll(re)).map<PathPart>(match => {\n // self casted RegexExpMatchArray to custom\n const typedMatch = match as unknown as [string, string] &\n RegExpExecArray & {\n groups:\n | {\n pathPart: undefined\n array: string\n nextType: '[' | '.' | ''\n }\n | {\n pathPart: string\n array: undefined\n nextType: '[' | '.' | ''\n }\n }\n const {array, pathPart, nextType} = typedMatch.groups\n const type = array === undefined ? 'object' : 'array'\n const nextDefault = nextType === '[' ? [] : {}\n return {\n path: array ?? pathPart,\n type,\n default: nextDefault,\n pathToPart: path.slice(0, typedMatch.index + typedMatch[1].length),\n }\n })\n}\n\n/**\n *\n * Returns the value accessed via `pathPart` in the `currentPathObject`\n * and a setter function to set the value in the `currentPathObject` via the\n * provided `pathPart`.\n *\n * @param pathPart - the path part to get the setter and getter for\n * @param currentPathObject - the object at the current path (before the path part)\n * @param arraysWithOrder - a set of arrays that have an order\n * @returns the setter and getter for the path part\n *\n * @example\n * ```ts\n * const pathPart = {path: 'a', type: 'object', default: {}, pathToPart: 'a'}\n * const currentPathObject = {}\n * const arraysWithOrder = new Set()\n * const [value, setValue] = getSetterAndGetter(pathPart, currentPathObject, arraysWithOrder)\n * setValue('b')\n * // => currentPathObject = {a: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const pathPart = {path: '0', type: 'array', default: [], pathToPart: 'a[0]'}\n * const currentPathObject = {a: []}\n * const arraysWithOrder = new Set()\n * const [value, setValue] = getSetterAndGetter(pathPart, currentPathObject, arraysWithOrder)\n * setValue('b')\n * // => currentPathObject = {a: ['b']}\n * ```\n *\n */\nfunction handlePathPart(\n pathPart: PathPart,\n currentPathObject: JsonArray | JsonObject,\n arraysWithOrder: Set<JsonArray>,\n): [\n nextPathValue: JsonValue | undefined,\n setNextPathValue: (value: JsonValue) => void,\n] {\n if (pathPart.type === 'object') {\n if (Array.isArray(currentPathObject)) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n const currentObject = currentPathObject\n return [\n currentObject[pathPart.path],\n val => (currentObject[pathPart.path] = val),\n ]\n }\n if (!Array.isArray(currentPathObject)) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n const currentArray = currentPathObject\n const isOrdered = pathPart.path !== ''\n\n const isOrderedArray = arraysWithOrder.has(currentArray)\n if (isOrdered) {\n arraysWithOrder.add(currentArray)\n }\n if (\n (!isOrdered && isOrderedArray) ||\n (isOrdered && !isOrderedArray && currentArray.length > 0)\n ) {\n throw new MixedArrayError(pathPart.pathToPart)\n }\n\n const order = isOrdered ? Number(pathPart.path) : currentArray.length\n return [currentArray[order], val => (currentArray[order] = val)]\n}\n\n/**\n *\n * Parses a FormData object to a JSON object. This is done by parsing the `name`\n * attribute of each `FormDataEntryValue` and then inserting the value at the\n * path. Also by default the start of the path is used to transform the value.\n *\n *\n * In front of the whole `key`:\n * - `+` => parse to `Number`\n * - `-` => set value to `null`\n * - `&` => parse to `Boolean`\n *\n * - `.` between path parts => nest into `objects`\n * - `[\\d*]` after path part => push to array in order `\\d` or push to end if `[]`\n *\n\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('+a', '1')\n * formData.append('&b', 'true')\n * formData.append('-c', 'null')\n * formData.append('d', 'foo')\n * parseFormData(formData, defaultTransform)\n * // => {a: 1, b: true, c: null, d: 'foo'}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a.b', 'foo')\n * parseFormData(formData)\n * // => {a: {b: 'foo'}}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * formData.append('a[1]', 'bar')\n * parseFormData(formData)\n * // => {a: ['foo', 'bar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[]', 'foo')\n * formData.append('a[]', 'bar')\n * parseFormData(formData)\n * // => {a: ['foo', 'bar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * parseFormData(formData, {transformEntry: (path, value) => {path, value: value + 'bar'}})\n * // => {a: ['foobar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * formData.append('a[1]', '')\n * parseFormData(formData, {removeEmptyString: true})\n * // => {a: ['foo']}\n * ```\n *\n * @param {Iterable<[string, string | File]>} formData - an iterator of an [`path`, `value`] tuple\n * - `path` := `^(\\+|\\-|\\&)?([^\\.]+?(\\[\\d*\\])*)(\\.[^\\.]+?(\\[\\d*\\])*)*$` (e.g. `+a[][1].b`)\n * - `value` := `string` or `File`\n * @param {ParseFormDataOptions} options - options for parsing the form data\n * - `transformEntry` - a function to transform the path and the value before\n * inserting the value at the path in the resulting object (default: `defaultTransform`)\n * - `removeEmptyString` - if `true` removes all entries where the value is an empty string\n * @returns {JsonObject} the parsed JSON object\n * @throws `DuplicateKeyError` if\n * - a path part is an object and the path part is already defined as an object\n * - a path part is an array and the path part is already defined as an array\n * @throws `MixedArrayError` if at a specific path part an unordered array is \n * defined and at a later path part an ordered array is defined or vice versa\n * - e.g. `a[0]` and `a[]`\n * - e.g. `a[]` and `a[0]`\n */\nexport function parseFormData(\n formData: Iterable<[string, string | File]>,\n {\n removeEmptyString = false,\n transformEntry = defaultTransform,\n }: ParseFormDataOptions = {},\n): JsonObject {\n const result: JsonObject = {}\n\n // all arrays we need to squash (in place) later\n const arraysWithOrder: Set<Array<JsonValue>> = new Set()\n\n for (const entry of Array.from(formData)) {\n if (removeEmptyString && entry[1] === '') continue\n\n const {path, value} = transformEntry(entry, defaultTransform)\n const pathParts = extractPathParts(path)\n\n let currentPathObject: JsonObject | JsonArray = result\n pathParts.forEach((pathPart, idx) => {\n const [nextPathValue, setNextPathValue] = handlePathPart(\n pathPart,\n currentPathObject,\n arraysWithOrder,\n )\n\n if (pathParts.length - 1 === idx) {\n if (nextPathValue !== undefined) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n setNextPathValue(value)\n } else {\n if (\n nextPathValue !== undefined &&\n !isJsonObject(nextPathValue) &&\n !Array.isArray(nextPathValue)\n ) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n\n const nextPathObject = nextPathValue ?? pathPart.default\n currentPathObject = nextPathObject\n setNextPathValue(nextPathObject)\n }\n })\n }\n\n for (const orderedArray of Array.from(arraysWithOrder)) {\n // replace array with a squashed array\n // array.flat(0) will remove all empty slots (e.g. [0, , 1] => [0, 1])\n orderedArray.splice(0, orderedArray.length, ...orderedArray.flat(0))\n }\n\n return result\n}\n"],"names":["DuplicateKeyError","Error","constructor","key","MixedArrayError","isJsonObject","val","Array","isArray","File","defaultTransform","entry","path","value","startsWith","slice","Number","Boolean","extractPathParts","re","from","matchAll","map","match","typedMatch","array","pathPart","nextType","groups","type","undefined","nextDefault","default","pathToPart","index","length","handlePathPart","currentPathObject","arraysWithOrder","currentObject","currentArray","isOrdered","isOrderedArray","has","add","order","parseFormData","formData","removeEmptyString","transformEntry","result","Set","pathParts","forEach","idx","nextPathValue","setNextPathValue","nextPathObject","orderedArray","splice","flat"],"mappings":";;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;EAIA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACO,MAAMA,iBAAiB,SAASC,KAAK,CAAC;IAE3CC,WAAW,CAACC,GAAW,EAAE;EACvB,IAAA,KAAK,CAAE,CAAA,2BAAA,EAA6BA,GAAI,CAAA,CAAC,CAAC,CAAA;EAAA,IAAA,IAAA,CAF5CA,GAAG,GAAA,KAAA,CAAA,CAAA;MAGD,IAAI,CAACA,GAAG,GAAGA,GAAG,CAAA;EAChB,GAAA;EACF,CAAA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACO,MAAMC,eAAe,SAASH,KAAK,CAAC;IAEzCC,WAAW,CAACC,GAAW,EAAE;EACvB,IAAA,KAAK,CAAE,CAAA,yBAAA,EAA2BA,GAAI,CAAA,CAAC,CAAC,CAAA;EAAA,IAAA,IAAA,CAF1CA,GAAG,GAAA,KAAA,CAAA,CAAA;MAGD,IAAI,CAACA,GAAG,GAAGA,GAAG,CAAA;EAChB,GAAA;EACF,CAAA;EAcA,SAASE,YAAY,CAACC,GAAc,EAAqB;IACvD,OACE,OAAOA,GAAG,KAAK,QAAQ,IACvB,CAACC,KAAK,CAACC,OAAO,CAACF,GAAG,CAAC,IACnBA,GAAG,KAAK,IAAI,IACZ,EAAEA,GAAG,YAAYG,YAAI,CAAC,CAAA;EAE1B,CAAA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAASC,gBAAgB,CAACC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,CAACH,YAAY,CAAC,CAAA;EACxD,EAAA,IAAIC,SAAS,EAAE;EACbH,IAAAA,eAAe,CAACM,GAAG,CAACJ,YAAY,CAAC,CAAA;EACnC,GAAA;EACA,EAAA,IACG,CAACC,SAAS,IAAIC,cAAc,IAC5BD,SAAS,IAAI,CAACC,cAAc,IAAIF,YAAY,CAACL,MAAM,GAAG,CAAE,EACzD;EACA,IAAA,MAAM,IAAI/B,eAAe,CAACsB,QAAQ,CAACO,UAAU,CAAC,CAAA;EAChD,GAAA;EAEA,EAAA,MAAMY,KAAK,GAAGJ,SAAS,GAAGzB,MAAM,CAACU,QAAQ,CAACd,IAAI,CAAC,GAAG4B,YAAY,CAACL,MAAM,CAAA;EACrE,EAAA,OAAO,CAACK,YAAY,CAACK,KAAK,CAAC,EAAEvC,GAAG,IAAKkC,YAAY,CAACK,KAAK,CAAC,GAAGvC,GAAI,CAAC,CAAA;EAClE,CAAA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACO,SAASwC,aAAa,CAC3BC,QAA2C,EAK/B,KAAA,EAAA;IAAA,IAJZ;EACEC,IAAAA,iBAAiB,GAAG,KAAK;EACzBC,IAAAA,cAAc,GAAGvC,gBAAAA;KACI,GAAA,KAAA,KAAA,KAAA,CAAA,GAAG,EAAE,GAAA,KAAA,CAAA;IAE5B,MAAMwC,MAAkB,GAAG,EAAE,CAAA;;EAE7B;EACA,EAAA,MAAMZ,eAAsC,GAAG,IAAIa,GAAG,EAAE,CAAA;IAExD,KAAK,MAAMxC,KAAK,IAAIJ,KAAK,CAACa,IAAI,CAAC2B,QAAQ,CAAC,EAAE;MACxC,IAAIC,iBAAiB,IAAIrC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,SAAA;MAE1C,MAAM;QAACC,IAAI;EAAEC,MAAAA,KAAAA;EAAK,KAAC,GAAGoC,cAAc,CAACtC,KAAK,EAAED,gBAAgB,CAAC,CAAA;EAC7D,IAAA,MAAM0C,SAAS,GAAGlC,gBAAgB,CAACN,IAAI,CAAC,CAAA;MAExC,IAAIyB,iBAAyC,GAAGa,MAAM,CAAA;EACtDE,IAAAA,SAAS,CAACC,OAAO,CAAC,CAAC3B,QAAQ,EAAE4B,GAAG,KAAK;EACnC,MAAA,MAAM,CAACC,aAAa,EAAEC,gBAAgB,CAAC,GAAGpB,cAAc,CACtDV,QAAQ,EACRW,iBAAiB,EACjBC,eAAe,CAChB,CAAA;EAED,MAAA,IAAIc,SAAS,CAACjB,MAAM,GAAG,CAAC,KAAKmB,GAAG,EAAE;UAChC,IAAIC,aAAa,KAAKzB,SAAS,EAAE;EAC/B,UAAA,MAAM,IAAI9B,iBAAiB,CAAC0B,QAAQ,CAACO,UAAU,CAAC,CAAA;EAClD,SAAA;UACAuB,gBAAgB,CAAC3C,KAAK,CAAC,CAAA;EACzB,OAAC,MAAM;EACL,QAAA,IACE0C,aAAa,KAAKzB,SAAS,IAC3B,CAACzB,YAAY,CAACkD,aAAa,CAAC,IAC5B,CAAChD,KAAK,CAACC,OAAO,CAAC+C,aAAa,CAAC,EAC7B;EACA,UAAA,MAAM,IAAIvD,iBAAiB,CAAC0B,QAAQ,CAACO,UAAU,CAAC,CAAA;EAClD,SAAA;EAEA,QAAA,MAAMwB,cAAc,GAAGF,aAAa,IAAI7B,QAAQ,CAACM,OAAO,CAAA;EACxDK,QAAAA,iBAAiB,GAAGoB,cAAc,CAAA;UAClCD,gBAAgB,CAACC,cAAc,CAAC,CAAA;EAClC,OAAA;EACF,KAAC,CAAC,CAAA;EACJ,GAAA;IAEA,KAAK,MAAMC,YAAY,IAAInD,KAAK,CAACa,IAAI,CAACkB,eAAe,CAAC,EAAE;EACtD;EACA;EACAoB,IAAAA,YAAY,CAACC,MAAM,CAAC,CAAC,EAAED,YAAY,CAACvB,MAAM,EAAE,GAAGuB,YAAY,CAACE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;EACtE,GAAA;EAEA,EAAA,OAAOV,MAAM,CAAA;EACf;;;;;;;;;;;;"}
1
+ {"version":3,"file":"parse-nested-form-data.umd.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @name parse-nested-form-data\n * @license MIT license.\n * @copyright (c) 2022 Christian Schurr\n * @author Christian Schurr <chris@schurr.dev>\n */\n\nimport {File} from '@remix-run/web-file'\n\n/**\n * Thrown when a path is used multiple times or has missmatching path parts.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'b')\n * formData.append('a[0]', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a[0]')\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'b')\n * formData.append('a', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a')\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'b')\n * formData.append('a[]', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a[]')\n * ```\n *\n */\nexport class DuplicateKeyError extends Error {\n key: string\n constructor(key: string) {\n super(`Duplicate key at path part ${key}`)\n this.key = key\n }\n}\n/**\n * Thrown when an array is used at the same path with an order parameter and\n * without an order parameter.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'a')\n * formData.append('a[]', 'b')\n * parseFormData(formData)\n * // => throws `MixedArrayError(a[])`\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[]', 'a')\n * formData.append('a[0]', 'b')\n * parseFormData(formData)\n * // => throws `MixedArrayError(a[0])`\n * ```\n */\nexport class MixedArrayError extends Error {\n key: string\n constructor(key: string) {\n super(`Mixed array at path part ${key}`)\n this.key = key\n }\n}\n/**\n * Thrown when a path part would access or assign a property on\n * `Object.prototype` (`__proto__`, `constructor`, `prototype`). Rejected\n * regardless of whether pollution would actually occur, to keep input\n * unambiguous.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('__proto__.polluted', 'yes')\n * parseFormData(formData)\n * // throws ForbiddenKeyError('__proto__')\n * ```\n */\nexport class ForbiddenKeyError extends Error {\n key: string\n constructor(key: string) {\n super(`Forbidden key at path part ${key}`)\n this.key = key\n }\n}\n\nconst FORBIDDEN_OBJECT_KEYS = new Set(['__proto__', 'constructor', 'prototype'])\n\ntype JsonObject = {[Key in string]?: JsonValue}\ntype JsonArray = Array<JsonValue>\ntype JsonValue =\n | string\n | number\n | JsonObject\n | JsonArray\n | boolean\n | null\n | File\ntype JsonLeafValue = Exclude<JsonValue, JsonArray | JsonObject>\n\nfunction isJsonObject(val: JsonValue): val is JsonObject {\n return (\n typeof val === 'object' &&\n !Array.isArray(val) &&\n val !== null &&\n !(val instanceof File)\n )\n}\n\n/**\n * Default Transformer for `parseFormData`.\n *\n * Transforms a FormData Entry into a path and a `JsonLeafValue`.\n *\n * - `path` starts with `+` -> transform value to `number`\n * - `path` starts with `&` -> transform value to `boolean`\n * - `path` starts with `-` -> transform value to `null`\n *\n * @example\n * ```ts\n * const entry = ['a[0]', 'b']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['+a[0]', '1']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: 1}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['&a[0]', 'true']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: true}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['-a[0]', 'null']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: null}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['a[0]', new File([''], 'file.txt')]\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: File}\n * ```\n *\n *\n * @param entry [path, value]: the FormData entry\n * @returns the path and the transformed value\n */\nfunction defaultTransform(entry: [path: string, value: string | File]): {\n path: string\n value: JsonLeafValue\n} {\n let path = entry[0]\n let value: JsonLeafValue = entry[1]\n if (path.startsWith('+')) {\n path = path.slice(1)\n value = Number(value)\n } else if (path.startsWith('&')) {\n path = path.slice(1)\n value = value === 'on' || value === 'true' || Boolean(Number(value))\n } else if (path.startsWith('-')) {\n path = path.slice(1)\n value = null\n }\n return {path, value}\n}\n\ntype DefaultTransform = typeof defaultTransform\n/**\n * Options to change the behavior of `parseFormData`.\n * @param transformEntry - a function to transform the FormData entry into a path and a value\n * (default: `defaultTransform`)\n * @param removeEmptyString - skip empty values '' (default: `false`)\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', '')\n * formData.append('b', 'b')\n * const result = parseFormData(formData, {removeEmptyString: true})\n * // => {b: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'a')\n * formData.append('b', 'b')\n * parseFormData(formData, {\n * transformEntry: ([path, value], defaultTransform) => {\n * return {\n * path,\n * value:\n * typeof value === 'string'\n * ? value.toUpperCase()\n * : defaultTransform(value),\n * }\n * },\n * })\n * // => {a: 'A', b: 'B'}\n * ```\n */\nexport type ParseFormDataOptions = {\n removeEmptyString?: boolean\n transformEntry?: (\n entry: [path: string, value: string | File],\n defaultTransform: DefaultTransform,\n ) => {path: string; value: JsonLeafValue}\n}\n\n/**\n * A parsed part of a FormData path. It consists of four parts:\n * @param path - the key to access the value in the intermediate result\n * @param type - the type of the path part (array - e.g. [\\d*], or object .e.g. .key)\n * @param default - the default value of the next path part ([] for array, {} for object)\n * @param pathToPart - the full path to the current path part\n *\n * @example\n * ```ts\n * const path = 'a[0].b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]\n * ```\n */\ntype PathPart = {\n path: string\n type: 'object' | 'array'\n default: {} | []\n pathToPart: string\n}\n/**\n *\n * Transforms a FormData path into an array of `PathPart`s.\n *\n * @param path - the path to extract the path parts from\n * @returns {Array<PathPart>} the extracted path parts\n *\n * @example\n * ```ts\n * const path = 'a[0].b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]\n * ```\n *\n * @example\n * ```ts\n * const path = 'a.b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: {}, pathToPart: 'a'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a.b'}]\n * ```\n *\n * @example\n * ```ts\n * const path = 'a[][0]'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '', type: 'array', default: [], pathToPart: 'a[]'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[][0]'}]\n * ```\n *\n *\n */\nfunction extractPathParts(path: string): Array<PathPart> {\n const re = /((?<array>\\d*)\\]|(?<pathPart>[^.[]+))(?<nextType>\\[|\\.|$)/g\n\n return Array.from(path.matchAll(re)).map<PathPart>(match => {\n // self casted RegexExpMatchArray to custom\n const typedMatch = match as unknown as [string, string] &\n RegExpExecArray & {\n groups:\n | {\n pathPart: undefined\n array: string\n nextType: '[' | '.' | ''\n }\n | {\n pathPart: string\n array: undefined\n nextType: '[' | '.' | ''\n }\n }\n const {array, pathPart, nextType} = typedMatch.groups\n const type = array === undefined ? 'object' : 'array'\n const nextDefault = nextType === '[' ? [] : {}\n return {\n path: array ?? pathPart,\n type,\n default: nextDefault,\n pathToPart: path.slice(0, typedMatch.index + typedMatch[1].length),\n }\n })\n}\n\n/**\n *\n * Returns the value accessed via `pathPart` in the `currentPathObject`\n * and a setter function to set the value in the `currentPathObject` via the\n * provided `pathPart`.\n *\n * @param pathPart - the path part to get the setter and getter for\n * @param currentPathObject - the object at the current path (before the path part)\n * @param arraysWithOrder - a set of arrays that have an order\n * @returns the setter and getter for the path part\n *\n * @example\n * ```ts\n * const pathPart = {path: 'a', type: 'object', default: {}, pathToPart: 'a'}\n * const currentPathObject = {}\n * const arraysWithOrder = new Set()\n * const [value, setValue] = getSetterAndGetter(pathPart, currentPathObject, arraysWithOrder)\n * setValue('b')\n * // => currentPathObject = {a: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const pathPart = {path: '0', type: 'array', default: [], pathToPart: 'a[0]'}\n * const currentPathObject = {a: []}\n * const arraysWithOrder = new Set()\n * const [value, setValue] = getSetterAndGetter(pathPart, currentPathObject, arraysWithOrder)\n * setValue('b')\n * // => currentPathObject = {a: ['b']}\n * ```\n *\n */\nfunction handlePathPart(\n pathPart: PathPart,\n currentPathObject: JsonArray | JsonObject,\n arraysWithOrder: Set<JsonArray>,\n): [\n nextPathValue: JsonValue | undefined,\n setNextPathValue: (value: JsonValue) => void,\n] {\n if (FORBIDDEN_OBJECT_KEYS.has(pathPart.path)) {\n throw new ForbiddenKeyError(pathPart.pathToPart)\n }\n if (pathPart.type === 'object') {\n if (Array.isArray(currentPathObject)) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n const currentObject = currentPathObject\n return [\n currentObject[pathPart.path],\n val => (currentObject[pathPart.path] = val),\n ]\n }\n if (!Array.isArray(currentPathObject)) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n const currentArray = currentPathObject\n const isOrdered = pathPart.path !== ''\n\n const isOrderedArray = arraysWithOrder.has(currentArray)\n if (isOrdered) {\n arraysWithOrder.add(currentArray)\n }\n if (\n (!isOrdered && isOrderedArray) ||\n (isOrdered && !isOrderedArray && currentArray.length > 0)\n ) {\n throw new MixedArrayError(pathPart.pathToPart)\n }\n\n const order = isOrdered ? Number(pathPart.path) : currentArray.length\n return [currentArray[order], val => (currentArray[order] = val)]\n}\n\n/**\n *\n * Parses a FormData object to a JSON object. This is done by parsing the `name`\n * attribute of each `FormDataEntryValue` and then inserting the value at the\n * path. Also by default the start of the path is used to transform the value.\n *\n *\n * In front of the whole `key`:\n * - `+` => parse to `Number`\n * - `-` => set value to `null`\n * - `&` => parse to `Boolean`\n *\n * - `.` between path parts => nest into `objects`\n * - `[\\d*]` after path part => push to array in order `\\d` or push to end if `[]`\n *\n\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('+a', '1')\n * formData.append('&b', 'true')\n * formData.append('-c', 'null')\n * formData.append('d', 'foo')\n * parseFormData(formData, defaultTransform)\n * // => {a: 1, b: true, c: null, d: 'foo'}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a.b', 'foo')\n * parseFormData(formData)\n * // => {a: {b: 'foo'}}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * formData.append('a[1]', 'bar')\n * parseFormData(formData)\n * // => {a: ['foo', 'bar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[]', 'foo')\n * formData.append('a[]', 'bar')\n * parseFormData(formData)\n * // => {a: ['foo', 'bar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * parseFormData(formData, {transformEntry: (path, value) => {path, value: value + 'bar'}})\n * // => {a: ['foobar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * formData.append('a[1]', '')\n * parseFormData(formData, {removeEmptyString: true})\n * // => {a: ['foo']}\n * ```\n *\n * @param {Iterable<[string, string | File]>} formData - an iterator of an [`path`, `value`] tuple\n * - `path` := `^(\\+|\\-|\\&)?([^\\.]+?(\\[\\d*\\])*)(\\.[^\\.]+?(\\[\\d*\\])*)*$` (e.g. `+a[][1].b`)\n * - `value` := `string` or `File`\n * @param {ParseFormDataOptions} options - options for parsing the form data\n * - `transformEntry` - a function to transform the path and the value before\n * inserting the value at the path in the resulting object (default: `defaultTransform`)\n * - `removeEmptyString` - if `true` removes all entries where the value is an empty string\n * @returns {JsonObject} the parsed JSON object\n * @throws `DuplicateKeyError` if\n * - a path part is an object and the path part is already defined as an object\n * - a path part is an array and the path part is already defined as an array\n * @throws `MixedArrayError` if at a specific path part an unordered array is \n * defined and at a later path part an ordered array is defined or vice versa\n * - e.g. `a[0]` and `a[]`\n * - e.g. `a[]` and `a[0]`\n */\nexport function parseFormData(\n formData: Iterable<[string, string | File]>,\n {\n removeEmptyString = false,\n transformEntry = defaultTransform,\n }: ParseFormDataOptions = {},\n): JsonObject {\n const result: JsonObject = {}\n\n // all arrays we need to squash (in place) later\n const arraysWithOrder: Set<Array<JsonValue>> = new Set()\n\n for (const entry of Array.from(formData)) {\n if (removeEmptyString && entry[1] === '') continue\n\n const {path, value} = transformEntry(entry, defaultTransform)\n const pathParts = extractPathParts(path)\n\n let currentPathObject: JsonObject | JsonArray = result\n pathParts.forEach((pathPart, idx) => {\n const [nextPathValue, setNextPathValue] = handlePathPart(\n pathPart,\n currentPathObject,\n arraysWithOrder,\n )\n\n if (pathParts.length - 1 === idx) {\n if (nextPathValue !== undefined) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n setNextPathValue(value)\n } else {\n if (\n nextPathValue !== undefined &&\n !isJsonObject(nextPathValue) &&\n !Array.isArray(nextPathValue)\n ) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n\n const nextPathObject = nextPathValue ?? pathPart.default\n currentPathObject = nextPathObject\n setNextPathValue(nextPathObject)\n }\n })\n }\n\n for (const orderedArray of Array.from(arraysWithOrder)) {\n // replace array with a squashed array\n // array.flat(0) will remove all empty slots (e.g. [0, , 1] => [0, 1])\n orderedArray.splice(0, orderedArray.length, ...orderedArray.flat(0))\n }\n\n return result\n}\n"],"names":["DuplicateKeyError","Error","constructor","key","MixedArrayError","ForbiddenKeyError","FORBIDDEN_OBJECT_KEYS","Set","isJsonObject","val","Array","isArray","File","defaultTransform","entry","path","value","startsWith","slice","Number","Boolean","extractPathParts","re","from","matchAll","map","match","typedMatch","array","pathPart","nextType","groups","type","undefined","nextDefault","default","pathToPart","index","length","handlePathPart","currentPathObject","arraysWithOrder","has","currentObject","currentArray","isOrdered","isOrderedArray","add","order","parseFormData","formData","removeEmptyString","transformEntry","result","pathParts","forEach","idx","nextPathValue","setNextPathValue","nextPathObject","orderedArray","splice","flat"],"mappings":"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;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EASA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAOA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAASK,gBAAgBA,CAACN,IAAY,EAAmB;IACvD,MAAMO,EAAE,GAAG,4DAA4D,CAAA;EAEvE,EAAA,OAAOZ,KAAK,CAACa,IAAI,CAACR,IAAI,CAACS,QAAQ,CAACF,EAAE,CAAC,CAAC,CAACG,GAAG,CAAWC,KAAK,IAAI;EAC1D;MACA,MAAMC,UAAU,GAAGD,KAahB,CAAA;MACH,MAAM;QAACE,KAAK;QAAEC,QAAQ;EAAEC,MAAAA,QAAAA;OAAS,GAAGH,UAAU,CAACI,MAAM,CAAA;MACrD,MAAMC,IAAI,GAAGJ,KAAK,KAAKK,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;MACrD,MAAMC,WAAW,GAAGJ,QAAQ,KAAK,GAAG,GAAG,EAAE,GAAG,EAAE,CAAA;MAC9C,OAAO;QACLf,IAAI,EAAEa,KAAK,IAAIC,QAAQ;QACvBG,IAAI;EACJG,MAAAA,OAAO,EAAED,WAAW;EACpBE,MAAAA,UAAU,EAAErB,IAAI,CAACG,KAAK,CAAC,CAAC,EAAES,UAAU,CAACU,KAAK,GAAGV,UAAU,CAAC,CAAC,CAAC,CAACW,MAAM,CAAA;OAClE,CAAA;EACH,GAAC,CAAC,CAAA;EACJ,CAAA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAASC,cAAcA,CACrBV,QAAkB,EAClBW,iBA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"}
@@ -1,8 +1,8 @@
1
- !function(t,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("@remix-run/web-file")):"function"==typeof define&&define.amd?define(["exports","@remix-run/web-file"],r):r((t="undefined"!=typeof globalThis?globalThis:t||self).parseNestedFormData={},t.RemixRunWebFile)}(this,(function(t,r){"use strict";
1
+ !function(t,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("@remix-run/web-file")):"function"==typeof define&&define.amd?define(["exports","@remix-run/web-file"],r):r((t="undefined"!=typeof globalThis?globalThis:t||self).parseNestedFormData={},t.RemixRunWebFile)}(this,function(t,r){"use strict";
2
2
  /**
3
3
  * @name parse-nested-form-data
4
4
  * @license MIT license.
5
5
  * @copyright (c) 2022 Christian Schurr
6
6
  * @author Christian Schurr <chris@schurr.dev>
7
- */class e extends Error{constructor(t){super(`Duplicate key at path part ${t}`),this.key=void 0,this.key=t}}class a extends Error{constructor(t){super(`Mixed array at path part ${t}`),this.key=void 0,this.key=t}}function o(t){let r=t[0],e=t[1];return r.startsWith("+")?(r=r.slice(1),e=Number(e)):r.startsWith("&")?(r=r.slice(1),e="on"===e||"true"===e||Boolean(Number(e))):r.startsWith("-")&&(r=r.slice(1),e=null),{path:r,value:e}}function n(t){return Array.from(t.matchAll(/((?<array>\d*)\]|(?<pathPart>[^.[]+))(?<nextType>\[|\.|$)/g)).map((r=>{const e=r,{array:a,pathPart:o,nextType:n}=e.groups;return{path:a??o,type:void 0===a?"object":"array",default:"["===n?[]:{},pathToPart:t.slice(0,e.index+e[1].length)}}))}function i(t,r,o){if("object"===t.type){if(Array.isArray(r))throw new e(t.pathToPart);const a=r;return[a[t.path],r=>a[t.path]=r]}if(!Array.isArray(r))throw new e(t.pathToPart);const n=r,i=""!==t.path,s=o.has(n);if(i&&o.add(n),!i&&s||i&&!s&&n.length>0)throw new a(t.pathToPart);const f=i?Number(t.path):n.length;return[n[f],t=>n[f]=t]}t.DuplicateKeyError=e,t.MixedArrayError=a,t.parseFormData=function(t,a){let{removeEmptyString:s=!1,transformEntry:f=o}=void 0===a?{}:a;const l={},p=new Set;for(const a of Array.from(t)){if(s&&""===a[1])continue;const{path:t,value:u}=f(a,o),c=n(t);let h=l;c.forEach(((t,a)=>{const[o,n]=i(t,h,p);if(c.length-1===a){if(void 0!==o)throw new e(t.pathToPart);n(u)}else{if(void 0!==o&&("object"!=typeof(s=o)||Array.isArray(s)||null===s||s instanceof r.File)&&!Array.isArray(o))throw new e(t.pathToPart);const a=o??t.default;h=a,n(a)}var s}))}for(const t of Array.from(p))t.splice(0,t.length,...t.flat(0));return l},Object.defineProperty(t,"__esModule",{value:!0})}));
7
+ */class e extends Error{constructor(t){super(`Duplicate key at path part ${t}`),this.key=void 0,this.key=t}}class o extends Error{constructor(t){super(`Mixed array at path part ${t}`),this.key=void 0,this.key=t}}class a extends Error{constructor(t){super(`Forbidden key at path part ${t}`),this.key=void 0,this.key=t}}const n=new Set(["__proto__","constructor","prototype"]);function i(t){let r=t[0],e=t[1];return r.startsWith("+")?(r=r.slice(1),e=Number(e)):r.startsWith("&")?(r=r.slice(1),e="on"===e||"true"===e||Boolean(Number(e))):r.startsWith("-")&&(r=r.slice(1),e=null),{path:r,value:e}}function s(t){return Array.from(t.matchAll(/((?<array>\d*)\]|(?<pathPart>[^.[]+))(?<nextType>\[|\.|$)/g)).map(r=>{const e=r,{array:o,pathPart:a,nextType:n}=e.groups;return{path:o??a,type:void 0===o?"object":"array",default:"["===n?[]:{},pathToPart:t.slice(0,e.index+e[1].length)}})}function p(t,r,i){if(n.has(t.path))throw new a(t.pathToPart);if("object"===t.type){if(Array.isArray(r))throw new e(t.pathToPart);const o=r;return[o[t.path],r=>o[t.path]=r]}if(!Array.isArray(r))throw new e(t.pathToPart);const s=r,p=""!==t.path,h=i.has(s);if(p&&i.add(s),!p&&h||p&&!h&&s.length>0)throw new o(t.pathToPart);const c=p?Number(t.path):s.length;return[s[c],t=>s[c]=t]}t.DuplicateKeyError=e,t.ForbiddenKeyError=a,t.MixedArrayError=o,t.parseFormData=function(t,{removeEmptyString:o=!1,transformEntry:a=i}={}){const n={},h=new Set;for(const c of Array.from(t)){if(o&&""===c[1])continue;const{path:t,value:u}=a(c,i),f=s(t);let l=n;f.forEach((t,o)=>{const[a,n]=p(t,l,h);if(f.length-1===o){if(void 0!==a)throw new e(t.pathToPart);n(u)}else{if(void 0!==a&&("object"!=typeof(i=a)||Array.isArray(i)||null===i||i instanceof r.File)&&!Array.isArray(a))throw new e(t.pathToPart);const o=a??t.default;l=o,n(o)}var i})}for(const t of Array.from(h))t.splice(0,t.length,...t.flat(0));return n},Object.defineProperty(t,"__esModule",{value:!0})});
8
8
  //# sourceMappingURL=parse-nested-form-data.umd.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse-nested-form-data.umd.min.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @name parse-nested-form-data\n * @license MIT license.\n * @copyright (c) 2022 Christian Schurr\n * @author Christian Schurr <chris@schurr.dev>\n */\n\nimport {File} from '@remix-run/web-file'\n\n/**\n * Thrown when a path is used multiple times or has missmatching path parts.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'b')\n * formData.append('a[0]', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a[0]')\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'b')\n * formData.append('a', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a')\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'b')\n * formData.append('a[]', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a[]')\n * ```\n *\n */\nexport class DuplicateKeyError extends Error {\n key: string\n constructor(key: string) {\n super(`Duplicate key at path part ${key}`)\n this.key = key\n }\n}\n/**\n * Thrown when an array is used at the same path with an order parameter and\n * without an order parameter.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'a')\n * formData.append('a[]', 'b')\n * parseFormData(formData)\n * // => throws `MixedArrayError(a[])`\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[]', 'a')\n * formData.append('a[0]', 'b')\n * parseFormData(formData)\n * // => throws `MixedArrayError(a[0])`\n * ```\n */\nexport class MixedArrayError extends Error {\n key: string\n constructor(key: string) {\n super(`Mixed array at path part ${key}`)\n this.key = key\n }\n}\n\ntype JsonObject = {[Key in string]?: JsonValue}\ntype JsonArray = Array<JsonValue>\ntype JsonValue =\n | string\n | number\n | JsonObject\n | JsonArray\n | boolean\n | null\n | File\ntype JsonLeafValue = Exclude<JsonValue, JsonArray | JsonObject>\n\nfunction isJsonObject(val: JsonValue): val is JsonObject {\n return (\n typeof val === 'object' &&\n !Array.isArray(val) &&\n val !== null &&\n !(val instanceof File)\n )\n}\n\n/**\n * Default Transformer for `parseFormData`.\n *\n * Transforms a FormData Entry into a path and a `JsonLeafValue`.\n *\n * - `path` starts with `+` -> transform value to `number`\n * - `path` starts with `&` -> transform value to `boolean`\n * - `path` starts with `-` -> transform value to `null`\n *\n * @example\n * ```ts\n * const entry = ['a[0]', 'b']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['+a[0]', '1']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: 1}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['&a[0]', 'true']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: true}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['-a[0]', 'null']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: null}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['a[0]', new File([''], 'file.txt')]\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: File}\n * ```\n *\n *\n * @param entry [path, value]: the FormData entry\n * @returns the path and the transformed value\n */\nfunction defaultTransform(entry: [path: string, value: string | File]): {\n path: string\n value: JsonLeafValue\n} {\n let path = entry[0]\n let value: JsonLeafValue = entry[1]\n if (path.startsWith('+')) {\n path = path.slice(1)\n value = Number(value)\n } else if (path.startsWith('&')) {\n path = path.slice(1)\n value = value === 'on' || value === 'true' || Boolean(Number(value))\n } else if (path.startsWith('-')) {\n path = path.slice(1)\n value = null\n }\n return {path, value}\n}\n\ntype DefaultTransform = typeof defaultTransform\n/**\n * Options to change the behavior of `parseFormData`.\n * @param transformEntry - a function to transform the FormData entry into a path and a value\n * (default: `defaultTransform`)\n * @param removeEmptyString - skip empty values '' (default: `false`)\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', '')\n * formData.append('b', 'b')\n * const result = parseFormData(formData, {removeEmptyString: true})\n * // => {b: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'a')\n * formData.append('b', 'b')\n * parseFormData(formData, {\n * transformEntry: ([path, value], defaultTransform) => {\n * return {\n * path,\n * value:\n * typeof value === 'string'\n * ? value.toUpperCase()\n * : defaultTransform(value),\n * }\n * },\n * })\n * // => {a: 'A', b: 'B'}\n * ```\n */\nexport type ParseFormDataOptions = {\n removeEmptyString?: boolean\n transformEntry?: (\n entry: [path: string, value: string | File],\n defaultTransform: DefaultTransform,\n ) => {path: string; value: JsonLeafValue}\n}\n\n/**\n * A parsed part of a FormData path. It consists of four parts:\n * @param path - the key to access the value in the intermediate result\n * @param type - the type of the path part (array - e.g. [\\d*], or object .e.g. .key)\n * @param default - the default value of the next path part ([] for array, {} for object)\n * @param pathToPart - the full path to the current path part\n *\n * @example\n * ```ts\n * const path = 'a[0].b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]\n * ```\n */\ntype PathPart = {\n path: string\n type: 'object' | 'array'\n default: {} | []\n pathToPart: string\n}\n/**\n *\n * Transforms a FormData path into an array of `PathPart`s.\n *\n * @param path - the path to extract the path parts from\n * @returns {Array<PathPart>} the extracted path parts\n *\n * @example\n * ```ts\n * const path = 'a[0].b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]\n * ```\n *\n * @example\n * ```ts\n * const path = 'a.b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: {}, pathToPart: 'a'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a.b'}]\n * ```\n *\n * @example\n * ```ts\n * const path = 'a[][0]'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '', type: 'array', default: [], pathToPart: 'a[]'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[][0]'}]\n * ```\n *\n *\n */\nfunction extractPathParts(path: string): Array<PathPart> {\n const re = /((?<array>\\d*)\\]|(?<pathPart>[^.[]+))(?<nextType>\\[|\\.|$)/g\n\n return Array.from(path.matchAll(re)).map<PathPart>(match => {\n // self casted RegexExpMatchArray to custom\n const typedMatch = match as unknown as [string, string] &\n RegExpExecArray & {\n groups:\n | {\n pathPart: undefined\n array: string\n nextType: '[' | '.' | ''\n }\n | {\n pathPart: string\n array: undefined\n nextType: '[' | '.' | ''\n }\n }\n const {array, pathPart, nextType} = typedMatch.groups\n const type = array === undefined ? 'object' : 'array'\n const nextDefault = nextType === '[' ? [] : {}\n return {\n path: array ?? pathPart,\n type,\n default: nextDefault,\n pathToPart: path.slice(0, typedMatch.index + typedMatch[1].length),\n }\n })\n}\n\n/**\n *\n * Returns the value accessed via `pathPart` in the `currentPathObject`\n * and a setter function to set the value in the `currentPathObject` via the\n * provided `pathPart`.\n *\n * @param pathPart - the path part to get the setter and getter for\n * @param currentPathObject - the object at the current path (before the path part)\n * @param arraysWithOrder - a set of arrays that have an order\n * @returns the setter and getter for the path part\n *\n * @example\n * ```ts\n * const pathPart = {path: 'a', type: 'object', default: {}, pathToPart: 'a'}\n * const currentPathObject = {}\n * const arraysWithOrder = new Set()\n * const [value, setValue] = getSetterAndGetter(pathPart, currentPathObject, arraysWithOrder)\n * setValue('b')\n * // => currentPathObject = {a: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const pathPart = {path: '0', type: 'array', default: [], pathToPart: 'a[0]'}\n * const currentPathObject = {a: []}\n * const arraysWithOrder = new Set()\n * const [value, setValue] = getSetterAndGetter(pathPart, currentPathObject, arraysWithOrder)\n * setValue('b')\n * // => currentPathObject = {a: ['b']}\n * ```\n *\n */\nfunction handlePathPart(\n pathPart: PathPart,\n currentPathObject: JsonArray | JsonObject,\n arraysWithOrder: Set<JsonArray>,\n): [\n nextPathValue: JsonValue | undefined,\n setNextPathValue: (value: JsonValue) => void,\n] {\n if (pathPart.type === 'object') {\n if (Array.isArray(currentPathObject)) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n const currentObject = currentPathObject\n return [\n currentObject[pathPart.path],\n val => (currentObject[pathPart.path] = val),\n ]\n }\n if (!Array.isArray(currentPathObject)) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n const currentArray = currentPathObject\n const isOrdered = pathPart.path !== ''\n\n const isOrderedArray = arraysWithOrder.has(currentArray)\n if (isOrdered) {\n arraysWithOrder.add(currentArray)\n }\n if (\n (!isOrdered && isOrderedArray) ||\n (isOrdered && !isOrderedArray && currentArray.length > 0)\n ) {\n throw new MixedArrayError(pathPart.pathToPart)\n }\n\n const order = isOrdered ? Number(pathPart.path) : currentArray.length\n return [currentArray[order], val => (currentArray[order] = val)]\n}\n\n/**\n *\n * Parses a FormData object to a JSON object. This is done by parsing the `name`\n * attribute of each `FormDataEntryValue` and then inserting the value at the\n * path. Also by default the start of the path is used to transform the value.\n *\n *\n * In front of the whole `key`:\n * - `+` => parse to `Number`\n * - `-` => set value to `null`\n * - `&` => parse to `Boolean`\n *\n * - `.` between path parts => nest into `objects`\n * - `[\\d*]` after path part => push to array in order `\\d` or push to end if `[]`\n *\n\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('+a', '1')\n * formData.append('&b', 'true')\n * formData.append('-c', 'null')\n * formData.append('d', 'foo')\n * parseFormData(formData, defaultTransform)\n * // => {a: 1, b: true, c: null, d: 'foo'}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a.b', 'foo')\n * parseFormData(formData)\n * // => {a: {b: 'foo'}}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * formData.append('a[1]', 'bar')\n * parseFormData(formData)\n * // => {a: ['foo', 'bar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[]', 'foo')\n * formData.append('a[]', 'bar')\n * parseFormData(formData)\n * // => {a: ['foo', 'bar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * parseFormData(formData, {transformEntry: (path, value) => {path, value: value + 'bar'}})\n * // => {a: ['foobar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * formData.append('a[1]', '')\n * parseFormData(formData, {removeEmptyString: true})\n * // => {a: ['foo']}\n * ```\n *\n * @param {Iterable<[string, string | File]>} formData - an iterator of an [`path`, `value`] tuple\n * - `path` := `^(\\+|\\-|\\&)?([^\\.]+?(\\[\\d*\\])*)(\\.[^\\.]+?(\\[\\d*\\])*)*$` (e.g. `+a[][1].b`)\n * - `value` := `string` or `File`\n * @param {ParseFormDataOptions} options - options for parsing the form data\n * - `transformEntry` - a function to transform the path and the value before\n * inserting the value at the path in the resulting object (default: `defaultTransform`)\n * - `removeEmptyString` - if `true` removes all entries where the value is an empty string\n * @returns {JsonObject} the parsed JSON object\n * @throws `DuplicateKeyError` if\n * - a path part is an object and the path part is already defined as an object\n * - a path part is an array and the path part is already defined as an array\n * @throws `MixedArrayError` if at a specific path part an unordered array is \n * defined and at a later path part an ordered array is defined or vice versa\n * - e.g. `a[0]` and `a[]`\n * - e.g. `a[]` and `a[0]`\n */\nexport function parseFormData(\n formData: Iterable<[string, string | File]>,\n {\n removeEmptyString = false,\n transformEntry = defaultTransform,\n }: ParseFormDataOptions = {},\n): JsonObject {\n const result: JsonObject = {}\n\n // all arrays we need to squash (in place) later\n const arraysWithOrder: Set<Array<JsonValue>> = new Set()\n\n for (const entry of Array.from(formData)) {\n if (removeEmptyString && entry[1] === '') continue\n\n const {path, value} = transformEntry(entry, defaultTransform)\n const pathParts = extractPathParts(path)\n\n let currentPathObject: JsonObject | JsonArray = result\n pathParts.forEach((pathPart, idx) => {\n const [nextPathValue, setNextPathValue] = handlePathPart(\n pathPart,\n currentPathObject,\n arraysWithOrder,\n )\n\n if (pathParts.length - 1 === idx) {\n if (nextPathValue !== undefined) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n setNextPathValue(value)\n } else {\n if (\n nextPathValue !== undefined &&\n !isJsonObject(nextPathValue) &&\n !Array.isArray(nextPathValue)\n ) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n\n const nextPathObject = nextPathValue ?? pathPart.default\n currentPathObject = nextPathObject\n setNextPathValue(nextPathObject)\n }\n })\n }\n\n for (const orderedArray of Array.from(arraysWithOrder)) {\n // replace array with a squashed array\n // array.flat(0) will remove all empty slots (e.g. [0, , 1] => [0, 1])\n orderedArray.splice(0, orderedArray.length, ...orderedArray.flat(0))\n }\n\n return result\n}\n"],"names":["DuplicateKeyError","Error","constructor","key","super","this","MixedArrayError","defaultTransform","entry","path","value","startsWith","slice","Number","Boolean","extractPathParts","Array","from","matchAll","map","match","typedMatch","array","pathPart","nextType","groups","type","undefined","default","pathToPart","index","length","handlePathPart","currentPathObject","arraysWithOrder","isArray","currentObject","val","currentArray","isOrdered","isOrderedArray","has","add","order","formData","_temp","removeEmptyString","transformEntry","result","Set","pathParts","forEach","idx","nextPathValue","setNextPathValue","File","nextPathObject","orderedArray","splice","flat"],"mappings":";;;;;;KAuCO,MAAMA,UAA0BC,MAErCC,YAAYC,GACVC,MAAO,8BAA6BD,KAAME,KAF5CF,SAAG,EAGDE,KAAKF,IAAMA,CACb,EAwBK,MAAMG,UAAwBL,MAEnCC,YAAYC,GACVC,MAAO,4BAA2BD,KAAME,KAF1CF,SAAG,EAGDE,KAAKF,IAAMA,CACb,EAwEF,SAASI,EAAiBC,GAIxB,IAAIC,EAAOD,EAAM,GACbE,EAAuBF,EAAM,GAWjC,OAVIC,EAAKE,WAAW,MAClBF,EAAOA,EAAKG,MAAM,GAClBF,EAAQG,OAAOH,IACND,EAAKE,WAAW,MACzBF,EAAOA,EAAKG,MAAM,GAClBF,EAAkB,OAAVA,GAA4B,SAAVA,GAAoBI,QAAQD,OAAOH,KACpDD,EAAKE,WAAW,OACzBF,EAAOA,EAAKG,MAAM,GAClBF,EAAQ,MAEH,CAACD,OAAMC,QAChB,CAsGA,SAASK,EAAiBN,GAGxB,OAAOO,MAAMC,KAAKR,EAAKS,SAFZ,+DAE0BC,KAAcC,IAEjD,MAAMC,EAAaD,GAcbE,MAACA,EAAKC,SAAEA,EAAQC,SAAEA,GAAYH,EAAWI,OAG/C,MAAO,CACLhB,KAAMa,GAASC,EACfG,UAJqBC,IAAVL,EAAsB,SAAW,QAK5CM,QAJ+B,MAAbJ,EAAmB,GAAK,CAAA,EAK1CK,WAAYpB,EAAKG,MAAM,EAAGS,EAAWS,MAAQT,EAAW,GAAGU,QAC5D,GAEL,CAkCA,SAASC,EACPT,EACAU,EACAC,GAKA,GAAsB,WAAlBX,EAASG,KAAmB,CAC9B,GAAIV,MAAMmB,QAAQF,GAChB,MAAM,IAAIjC,EAAkBuB,EAASM,YAEvC,MAAMO,EAAgBH,EACtB,MAAO,CACLG,EAAcb,EAASd,MACvB4B,GAAQD,EAAcb,EAASd,MAAQ4B,EAE3C,CACA,IAAKrB,MAAMmB,QAAQF,GACjB,MAAM,IAAIjC,EAAkBuB,EAASM,YAEvC,MAAMS,EAAeL,EACfM,EAA8B,KAAlBhB,EAASd,KAErB+B,EAAiBN,EAAgBO,IAAIH,GAI3C,GAHIC,GACFL,EAAgBQ,IAAIJ,IAGlBC,GAAaC,GACdD,IAAcC,GAAkBF,EAAaP,OAAS,EAEvD,MAAM,IAAIzB,EAAgBiB,EAASM,YAGrC,MAAMc,EAAQJ,EAAY1B,OAAOU,EAASd,MAAQ6B,EAAaP,OAC/D,MAAO,CAACO,EAAaK,GAAQN,GAAQC,EAAaK,GAASN,EAC7D,2DAyFO,SACLO,EAKYC,GAAA,IAJZC,kBACEA,GAAoB,EAAKC,eACzBA,EAAiBxC,QACI,IAAAsC,EAAG,CAAA,EAAEA,EAE5B,MAAMG,EAAqB,CAAA,EAGrBd,EAAyC,IAAIe,IAEnD,IAAK,MAAMzC,KAASQ,MAAMC,KAAK2B,GAAW,CACxC,GAAIE,GAAkC,KAAbtC,EAAM,GAAW,SAE1C,MAAMC,KAACA,EAAIC,MAAEA,GAASqC,EAAevC,EAAOD,GACtC2C,EAAYnC,EAAiBN,GAEnC,IAAIwB,EAA4Ce,EAChDE,EAAUC,SAAQ,CAAC5B,EAAU6B,KAC3B,MAAOC,EAAeC,GAAoBtB,EACxCT,EACAU,EACAC,GAGF,GAAIgB,EAAUnB,OAAS,IAAMqB,EAAK,CAChC,QAAsBzB,IAAlB0B,EACF,MAAM,IAAIrD,EAAkBuB,EAASM,YAEvCyB,EAAiB5C,EACnB,KAAO,CACL,QACoBiB,IAAlB0B,IA5YS,iBAFGhB,EA+YEgB,IA5YnBrC,MAAMmB,QAAQE,IACP,OAARA,GACEA,aAAekB,EAAIA,QA2YdvC,MAAMmB,QAAQkB,GAEf,MAAM,IAAIrD,EAAkBuB,EAASM,YAGvC,MAAM2B,EAAiBH,GAAiB9B,EAASK,QACjDK,EAAoBuB,EACpBF,EAAiBE,EACnB,CAxZN,IAAsBnB,CAwZhB,GAEJ,CAEA,IAAK,MAAMoB,KAAgBzC,MAAMC,KAAKiB,GAGpCuB,EAAaC,OAAO,EAAGD,EAAa1B,UAAW0B,EAAaE,KAAK,IAGnE,OAAOX,CACT"}
1
+ {"version":3,"file":"parse-nested-form-data.umd.min.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @name parse-nested-form-data\n * @license MIT license.\n * @copyright (c) 2022 Christian Schurr\n * @author Christian Schurr <chris@schurr.dev>\n */\n\nimport {File} from '@remix-run/web-file'\n\n/**\n * Thrown when a path is used multiple times or has missmatching path parts.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'b')\n * formData.append('a[0]', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a[0]')\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'b')\n * formData.append('a', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a')\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'b')\n * formData.append('a[]', 'c')\n * parseFormData(formData)\n * // throws DuplicateKeyError('a[]')\n * ```\n *\n */\nexport class DuplicateKeyError extends Error {\n key: string\n constructor(key: string) {\n super(`Duplicate key at path part ${key}`)\n this.key = key\n }\n}\n/**\n * Thrown when an array is used at the same path with an order parameter and\n * without an order parameter.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'a')\n * formData.append('a[]', 'b')\n * parseFormData(formData)\n * // => throws `MixedArrayError(a[])`\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[]', 'a')\n * formData.append('a[0]', 'b')\n * parseFormData(formData)\n * // => throws `MixedArrayError(a[0])`\n * ```\n */\nexport class MixedArrayError extends Error {\n key: string\n constructor(key: string) {\n super(`Mixed array at path part ${key}`)\n this.key = key\n }\n}\n/**\n * Thrown when a path part would access or assign a property on\n * `Object.prototype` (`__proto__`, `constructor`, `prototype`). Rejected\n * regardless of whether pollution would actually occur, to keep input\n * unambiguous.\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('__proto__.polluted', 'yes')\n * parseFormData(formData)\n * // throws ForbiddenKeyError('__proto__')\n * ```\n */\nexport class ForbiddenKeyError extends Error {\n key: string\n constructor(key: string) {\n super(`Forbidden key at path part ${key}`)\n this.key = key\n }\n}\n\nconst FORBIDDEN_OBJECT_KEYS = new Set(['__proto__', 'constructor', 'prototype'])\n\ntype JsonObject = {[Key in string]?: JsonValue}\ntype JsonArray = Array<JsonValue>\ntype JsonValue =\n | string\n | number\n | JsonObject\n | JsonArray\n | boolean\n | null\n | File\ntype JsonLeafValue = Exclude<JsonValue, JsonArray | JsonObject>\n\nfunction isJsonObject(val: JsonValue): val is JsonObject {\n return (\n typeof val === 'object' &&\n !Array.isArray(val) &&\n val !== null &&\n !(val instanceof File)\n )\n}\n\n/**\n * Default Transformer for `parseFormData`.\n *\n * Transforms a FormData Entry into a path and a `JsonLeafValue`.\n *\n * - `path` starts with `+` -> transform value to `number`\n * - `path` starts with `&` -> transform value to `boolean`\n * - `path` starts with `-` -> transform value to `null`\n *\n * @example\n * ```ts\n * const entry = ['a[0]', 'b']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['+a[0]', '1']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: 1}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['&a[0]', 'true']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: true}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['-a[0]', 'null']\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: null}\n * ```\n *\n * @example\n * ```ts\n * const entry = ['a[0]', new File([''], 'file.txt')]\n * const result = defaultTransform(entry)\n * // => {path: 'a[0]', value: File}\n * ```\n *\n *\n * @param entry [path, value]: the FormData entry\n * @returns the path and the transformed value\n */\nfunction defaultTransform(entry: [path: string, value: string | File]): {\n path: string\n value: JsonLeafValue\n} {\n let path = entry[0]\n let value: JsonLeafValue = entry[1]\n if (path.startsWith('+')) {\n path = path.slice(1)\n value = Number(value)\n } else if (path.startsWith('&')) {\n path = path.slice(1)\n value = value === 'on' || value === 'true' || Boolean(Number(value))\n } else if (path.startsWith('-')) {\n path = path.slice(1)\n value = null\n }\n return {path, value}\n}\n\ntype DefaultTransform = typeof defaultTransform\n/**\n * Options to change the behavior of `parseFormData`.\n * @param transformEntry - a function to transform the FormData entry into a path and a value\n * (default: `defaultTransform`)\n * @param removeEmptyString - skip empty values '' (default: `false`)\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', '')\n * formData.append('b', 'b')\n * const result = parseFormData(formData, {removeEmptyString: true})\n * // => {b: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a', 'a')\n * formData.append('b', 'b')\n * parseFormData(formData, {\n * transformEntry: ([path, value], defaultTransform) => {\n * return {\n * path,\n * value:\n * typeof value === 'string'\n * ? value.toUpperCase()\n * : defaultTransform(value),\n * }\n * },\n * })\n * // => {a: 'A', b: 'B'}\n * ```\n */\nexport type ParseFormDataOptions = {\n removeEmptyString?: boolean\n transformEntry?: (\n entry: [path: string, value: string | File],\n defaultTransform: DefaultTransform,\n ) => {path: string; value: JsonLeafValue}\n}\n\n/**\n * A parsed part of a FormData path. It consists of four parts:\n * @param path - the key to access the value in the intermediate result\n * @param type - the type of the path part (array - e.g. [\\d*], or object .e.g. .key)\n * @param default - the default value of the next path part ([] for array, {} for object)\n * @param pathToPart - the full path to the current path part\n *\n * @example\n * ```ts\n * const path = 'a[0].b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]\n * ```\n */\ntype PathPart = {\n path: string\n type: 'object' | 'array'\n default: {} | []\n pathToPart: string\n}\n/**\n *\n * Transforms a FormData path into an array of `PathPart`s.\n *\n * @param path - the path to extract the path parts from\n * @returns {Array<PathPart>} the extracted path parts\n *\n * @example\n * ```ts\n * const path = 'a[0].b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[0]'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a[0].b'}]\n * ```\n *\n * @example\n * ```ts\n * const path = 'a.b'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: {}, pathToPart: 'a'},\n * // {path: 'b', type: 'object', default: {}, pathToPart: 'a.b'}]\n * ```\n *\n * @example\n * ```ts\n * const path = 'a[][0]'\n * const result = extractPathParts(path)\n * // => [{path: 'a', type: 'object, default: [], pathToPart: 'a'},\n * // {path: '', type: 'array', default: [], pathToPart: 'a[]'},\n * // {path: '0', type: 'array', default: {}, pathToPart: 'a[][0]'}]\n * ```\n *\n *\n */\nfunction extractPathParts(path: string): Array<PathPart> {\n const re = /((?<array>\\d*)\\]|(?<pathPart>[^.[]+))(?<nextType>\\[|\\.|$)/g\n\n return Array.from(path.matchAll(re)).map<PathPart>(match => {\n // self casted RegexExpMatchArray to custom\n const typedMatch = match as unknown as [string, string] &\n RegExpExecArray & {\n groups:\n | {\n pathPart: undefined\n array: string\n nextType: '[' | '.' | ''\n }\n | {\n pathPart: string\n array: undefined\n nextType: '[' | '.' | ''\n }\n }\n const {array, pathPart, nextType} = typedMatch.groups\n const type = array === undefined ? 'object' : 'array'\n const nextDefault = nextType === '[' ? [] : {}\n return {\n path: array ?? pathPart,\n type,\n default: nextDefault,\n pathToPart: path.slice(0, typedMatch.index + typedMatch[1].length),\n }\n })\n}\n\n/**\n *\n * Returns the value accessed via `pathPart` in the `currentPathObject`\n * and a setter function to set the value in the `currentPathObject` via the\n * provided `pathPart`.\n *\n * @param pathPart - the path part to get the setter and getter for\n * @param currentPathObject - the object at the current path (before the path part)\n * @param arraysWithOrder - a set of arrays that have an order\n * @returns the setter and getter for the path part\n *\n * @example\n * ```ts\n * const pathPart = {path: 'a', type: 'object', default: {}, pathToPart: 'a'}\n * const currentPathObject = {}\n * const arraysWithOrder = new Set()\n * const [value, setValue] = getSetterAndGetter(pathPart, currentPathObject, arraysWithOrder)\n * setValue('b')\n * // => currentPathObject = {a: 'b'}\n * ```\n *\n * @example\n * ```ts\n * const pathPart = {path: '0', type: 'array', default: [], pathToPart: 'a[0]'}\n * const currentPathObject = {a: []}\n * const arraysWithOrder = new Set()\n * const [value, setValue] = getSetterAndGetter(pathPart, currentPathObject, arraysWithOrder)\n * setValue('b')\n * // => currentPathObject = {a: ['b']}\n * ```\n *\n */\nfunction handlePathPart(\n pathPart: PathPart,\n currentPathObject: JsonArray | JsonObject,\n arraysWithOrder: Set<JsonArray>,\n): [\n nextPathValue: JsonValue | undefined,\n setNextPathValue: (value: JsonValue) => void,\n] {\n if (FORBIDDEN_OBJECT_KEYS.has(pathPart.path)) {\n throw new ForbiddenKeyError(pathPart.pathToPart)\n }\n if (pathPart.type === 'object') {\n if (Array.isArray(currentPathObject)) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n const currentObject = currentPathObject\n return [\n currentObject[pathPart.path],\n val => (currentObject[pathPart.path] = val),\n ]\n }\n if (!Array.isArray(currentPathObject)) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n const currentArray = currentPathObject\n const isOrdered = pathPart.path !== ''\n\n const isOrderedArray = arraysWithOrder.has(currentArray)\n if (isOrdered) {\n arraysWithOrder.add(currentArray)\n }\n if (\n (!isOrdered && isOrderedArray) ||\n (isOrdered && !isOrderedArray && currentArray.length > 0)\n ) {\n throw new MixedArrayError(pathPart.pathToPart)\n }\n\n const order = isOrdered ? Number(pathPart.path) : currentArray.length\n return [currentArray[order], val => (currentArray[order] = val)]\n}\n\n/**\n *\n * Parses a FormData object to a JSON object. This is done by parsing the `name`\n * attribute of each `FormDataEntryValue` and then inserting the value at the\n * path. Also by default the start of the path is used to transform the value.\n *\n *\n * In front of the whole `key`:\n * - `+` => parse to `Number`\n * - `-` => set value to `null`\n * - `&` => parse to `Boolean`\n *\n * - `.` between path parts => nest into `objects`\n * - `[\\d*]` after path part => push to array in order `\\d` or push to end if `[]`\n *\n\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('+a', '1')\n * formData.append('&b', 'true')\n * formData.append('-c', 'null')\n * formData.append('d', 'foo')\n * parseFormData(formData, defaultTransform)\n * // => {a: 1, b: true, c: null, d: 'foo'}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a.b', 'foo')\n * parseFormData(formData)\n * // => {a: {b: 'foo'}}\n * ```\n *\n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * formData.append('a[1]', 'bar')\n * parseFormData(formData)\n * // => {a: ['foo', 'bar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[]', 'foo')\n * formData.append('a[]', 'bar')\n * parseFormData(formData)\n * // => {a: ['foo', 'bar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * parseFormData(formData, {transformEntry: (path, value) => {path, value: value + 'bar'}})\n * // => {a: ['foobar']}\n * ```\n * \n * @example\n * ```ts\n * const formData = new FormData()\n * formData.append('a[0]', 'foo')\n * formData.append('a[1]', '')\n * parseFormData(formData, {removeEmptyString: true})\n * // => {a: ['foo']}\n * ```\n *\n * @param {Iterable<[string, string | File]>} formData - an iterator of an [`path`, `value`] tuple\n * - `path` := `^(\\+|\\-|\\&)?([^\\.]+?(\\[\\d*\\])*)(\\.[^\\.]+?(\\[\\d*\\])*)*$` (e.g. `+a[][1].b`)\n * - `value` := `string` or `File`\n * @param {ParseFormDataOptions} options - options for parsing the form data\n * - `transformEntry` - a function to transform the path and the value before\n * inserting the value at the path in the resulting object (default: `defaultTransform`)\n * - `removeEmptyString` - if `true` removes all entries where the value is an empty string\n * @returns {JsonObject} the parsed JSON object\n * @throws `DuplicateKeyError` if\n * - a path part is an object and the path part is already defined as an object\n * - a path part is an array and the path part is already defined as an array\n * @throws `MixedArrayError` if at a specific path part an unordered array is \n * defined and at a later path part an ordered array is defined or vice versa\n * - e.g. `a[0]` and `a[]`\n * - e.g. `a[]` and `a[0]`\n */\nexport function parseFormData(\n formData: Iterable<[string, string | File]>,\n {\n removeEmptyString = false,\n transformEntry = defaultTransform,\n }: ParseFormDataOptions = {},\n): JsonObject {\n const result: JsonObject = {}\n\n // all arrays we need to squash (in place) later\n const arraysWithOrder: Set<Array<JsonValue>> = new Set()\n\n for (const entry of Array.from(formData)) {\n if (removeEmptyString && entry[1] === '') continue\n\n const {path, value} = transformEntry(entry, defaultTransform)\n const pathParts = extractPathParts(path)\n\n let currentPathObject: JsonObject | JsonArray = result\n pathParts.forEach((pathPart, idx) => {\n const [nextPathValue, setNextPathValue] = handlePathPart(\n pathPart,\n currentPathObject,\n arraysWithOrder,\n )\n\n if (pathParts.length - 1 === idx) {\n if (nextPathValue !== undefined) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n setNextPathValue(value)\n } else {\n if (\n nextPathValue !== undefined &&\n !isJsonObject(nextPathValue) &&\n !Array.isArray(nextPathValue)\n ) {\n throw new DuplicateKeyError(pathPart.pathToPart)\n }\n\n const nextPathObject = nextPathValue ?? pathPart.default\n currentPathObject = nextPathObject\n setNextPathValue(nextPathObject)\n }\n })\n }\n\n for (const orderedArray of Array.from(arraysWithOrder)) {\n // replace array with a squashed array\n // array.flat(0) will remove all empty slots (e.g. [0, , 1] => [0, 1])\n orderedArray.splice(0, orderedArray.length, ...orderedArray.flat(0))\n }\n\n return result\n}\n"],"names":["DuplicateKeyError","Error","constructor","key","super","this","MixedArrayError","ForbiddenKeyError","FORBIDDEN_OBJECT_KEYS","Set","defaultTransform","entry","path","value","startsWith","slice","Number","Boolean","extractPathParts","Array","from","matchAll","map","match","typedMatch","array","pathPart","nextType","groups","type","undefined","default","pathToPart","index","length","handlePathPart","currentPathObject","arraysWithOrder","has","isArray","currentObject","val","currentArray","isOrdered","isOrderedArray","add","order","formData","removeEmptyString","transformEntry","result","pathParts","forEach","idx","nextPathValue","setNextPathValue","File","nextPathObject","orderedArray","splice","flat"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parse-nested-form-data",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A tiny node module for parsing FormData by name into objects and arrays",
5
5
  "main": "dist/parse-nested-form-data.cjs.js",
6
6
  "module": "dist/parse-nested-form-data.esm.js",
@@ -44,6 +44,7 @@
44
44
  "devDependencies": {
45
45
  "@remix-run/web-file": "^3.0.2",
46
46
  "@remix-run/web-form-data": "^3.0.3",
47
+ "@types/minimatch": "^3.0.5",
47
48
  "kcd-scripts": "^12.3.0",
48
49
  "rimraf": "^3.0.2",
49
50
  "typescript": "^4.8.4"