parse-nested-form-data 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +309 -174
- package/dist/index.d.ts +131 -36
- package/dist/parse-nested-form-data.cjs.js +576 -445
- package/dist/parse-nested-form-data.esm.js +571 -442
- package/dist/parse-nested-form-data.umd.js +607 -475
- package/dist/parse-nested-form-data.umd.js.map +1 -1
- package/dist/parse-nested-form-data.umd.min.js +7 -7
- package/dist/parse-nested-form-data.umd.min.js.map +1 -1
- package/package.json +108 -54
- package/CHANGELOG.md +0 -13
- package/dist/__tests__/index.d.ts +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @author Christian Schurr <chris@schurr.dev>
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
* Thrown when a path is used multiple times or has
|
|
8
|
+
* Thrown when a path is used multiple times or has mismatching path parts.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```ts
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* formData.append('a', 'c')
|
|
24
24
|
* parseFormData(formData)
|
|
25
25
|
* // throws DuplicateKeyError('a')
|
|
26
|
+
* ```
|
|
26
27
|
*
|
|
27
28
|
* @example
|
|
28
29
|
* ```ts
|
|
@@ -35,8 +36,8 @@
|
|
|
35
36
|
*
|
|
36
37
|
*/
|
|
37
38
|
export declare class DuplicateKeyError extends Error {
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
key: string;
|
|
40
|
+
constructor(key: string);
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Thrown when an array is used at the same path with an order parameter and
|
|
@@ -61,14 +62,16 @@ export declare class DuplicateKeyError extends Error {
|
|
|
61
62
|
* ```
|
|
62
63
|
*/
|
|
63
64
|
export declare class MixedArrayError extends Error {
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
key: string;
|
|
66
|
+
constructor(key: string);
|
|
66
67
|
}
|
|
67
68
|
/**
|
|
68
|
-
* Thrown when
|
|
69
|
-
* `
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* Thrown when an object path part is `__proto__`, `constructor` or
|
|
70
|
+
* `prototype`, which could modify `Object.prototype`. The names are rejected
|
|
71
|
+
* at any position in the path. Entries with other names on `Object.prototype`
|
|
72
|
+
* (`toString`, ...) are removed instead (see `parseFormData`).
|
|
73
|
+
* `key` is the path up to the forbidden part, cut to 100 characters plus `...`
|
|
74
|
+
* when it's longer.
|
|
72
75
|
*
|
|
73
76
|
* @example
|
|
74
77
|
* ```ts
|
|
@@ -79,12 +82,48 @@ export declare class MixedArrayError extends Error {
|
|
|
79
82
|
* ```
|
|
80
83
|
*/
|
|
81
84
|
export declare class ForbiddenKeyError extends Error {
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
key: string;
|
|
86
|
+
constructor(key: string);
|
|
84
87
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Thrown when a path has more path parts than the `depth` option allows.
|
|
90
|
+
* `key` is the path up to the first path part over the limit, cut to
|
|
91
|
+
* 100 characters plus `...` when it's longer.
|
|
92
|
+
* Within one entry the depth is checked first, so a path that's too deep throws
|
|
93
|
+
* `DepthLimitError` even if it would also throw another error, like a
|
|
94
|
+
* `ForbiddenKeyError` for `__proto__`.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const formData = new FormData()
|
|
99
|
+
* formData.append('a.b[0].c', 'x')
|
|
100
|
+
* parseFormData(formData, {depth: 2})
|
|
101
|
+
* // throws DepthLimitError('a.b[0]')
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export declare class DepthLimitError extends Error {
|
|
105
|
+
key: string;
|
|
106
|
+
constructor(key: string);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Thrown when an array index in a path is larger than `4294967294`, the
|
|
110
|
+
* largest index a JavaScript array can hold.
|
|
111
|
+
* `key` is the path up to that array part, cut to 100 characters plus `...`
|
|
112
|
+
* when it's longer.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const formData = new FormData()
|
|
117
|
+
* formData.append('a[4294967295]', 'b')
|
|
118
|
+
* parseFormData(formData)
|
|
119
|
+
* // throws InvalidPathError('a[4294967295]')
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare class InvalidPathError extends Error {
|
|
123
|
+
key: string;
|
|
124
|
+
constructor(key: string);
|
|
125
|
+
}
|
|
126
|
+
type JsonObject = { [Key in string]?: JsonValue; };
|
|
88
127
|
type JsonArray = Array<JsonValue>;
|
|
89
128
|
type JsonValue = string | number | JsonObject | JsonArray | boolean | null | File;
|
|
90
129
|
type JsonLeafValue = Exclude<JsonValue, JsonArray | JsonObject>;
|
|
@@ -137,8 +176,8 @@ type JsonLeafValue = Exclude<JsonValue, JsonArray | JsonObject>;
|
|
|
137
176
|
* @returns the path and the transformed value
|
|
138
177
|
*/
|
|
139
178
|
declare function defaultTransform(entry: [path: string, value: string | File]): {
|
|
140
|
-
|
|
141
|
-
|
|
179
|
+
path: string;
|
|
180
|
+
value: JsonLeafValue;
|
|
142
181
|
};
|
|
143
182
|
type DefaultTransform = typeof defaultTransform;
|
|
144
183
|
/**
|
|
@@ -146,6 +185,20 @@ type DefaultTransform = typeof defaultTransform;
|
|
|
146
185
|
* @param transformEntry - a function to transform the FormData entry into a path and a value
|
|
147
186
|
* (default: `defaultTransform`)
|
|
148
187
|
* @param removeEmptyString - skip empty values '' (default: `false`)
|
|
188
|
+
* @param depth - the maximum number of path parts in a path, counting every
|
|
189
|
+
* part (`a.b[0]` has 3); a positive integer or `Infinity` (default: `Infinity`).
|
|
190
|
+
* For untrusted input, `32` is recommended; 2.0 will lower the default.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* const formData = new FormData()
|
|
195
|
+
* formData.append('a.b', 'x')
|
|
196
|
+
* parseFormData(formData, {depth: 2})
|
|
197
|
+
* // => {a: {b: 'x'}}
|
|
198
|
+
* formData.append('a.c.d', 'y')
|
|
199
|
+
* parseFormData(formData, {depth: 2})
|
|
200
|
+
* // throws DepthLimitError('a.c.d')
|
|
201
|
+
* ```
|
|
149
202
|
*
|
|
150
203
|
* @example
|
|
151
204
|
* ```ts
|
|
@@ -162,13 +215,11 @@ type DefaultTransform = typeof defaultTransform;
|
|
|
162
215
|
* formData.append('a', 'a')
|
|
163
216
|
* formData.append('b', 'b')
|
|
164
217
|
* parseFormData(formData, {
|
|
165
|
-
* transformEntry: (
|
|
218
|
+
* transformEntry: (entry, defaultTransform) => {
|
|
219
|
+
* const {path, value} = defaultTransform(entry)
|
|
166
220
|
* return {
|
|
167
221
|
* path,
|
|
168
|
-
* value:
|
|
169
|
-
* typeof value === 'string'
|
|
170
|
-
* ? value.toUpperCase()
|
|
171
|
-
* : defaultTransform(value),
|
|
222
|
+
* value: typeof value === 'string' ? value.toUpperCase() : value,
|
|
172
223
|
* }
|
|
173
224
|
* },
|
|
174
225
|
* })
|
|
@@ -176,11 +227,12 @@ type DefaultTransform = typeof defaultTransform;
|
|
|
176
227
|
* ```
|
|
177
228
|
*/
|
|
178
229
|
export type ParseFormDataOptions = {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
230
|
+
removeEmptyString?: boolean;
|
|
231
|
+
depth?: number;
|
|
232
|
+
transformEntry?: (entry: [path: string, value: string | File], defaultTransform: DefaultTransform) => {
|
|
233
|
+
path: string;
|
|
234
|
+
value: JsonLeafValue;
|
|
235
|
+
};
|
|
184
236
|
};
|
|
185
237
|
/**
|
|
186
238
|
*
|
|
@@ -195,9 +247,9 @@ export type ParseFormDataOptions = {
|
|
|
195
247
|
* - `&` => parse to `Boolean`
|
|
196
248
|
*
|
|
197
249
|
* - `.` between path parts => nest into `objects`
|
|
250
|
+
* - `[key]` after path part => nest into `objects`, same as `.key`; the text
|
|
251
|
+
* in brackets is the key as it is (`a[b.c]` => key `b.c`)
|
|
198
252
|
* - `[\d*]` after path part => push to array in order `\d` or push to end if `[]`
|
|
199
|
-
*
|
|
200
|
-
|
|
201
253
|
*
|
|
202
254
|
* @example
|
|
203
255
|
* ```ts
|
|
@@ -206,7 +258,7 @@ export type ParseFormDataOptions = {
|
|
|
206
258
|
* formData.append('&b', 'true')
|
|
207
259
|
* formData.append('-c', 'null')
|
|
208
260
|
* formData.append('d', 'foo')
|
|
209
|
-
* parseFormData(formData
|
|
261
|
+
* parseFormData(formData)
|
|
210
262
|
* // => {a: 1, b: true, c: null, d: 'foo'}
|
|
211
263
|
* ```
|
|
212
264
|
*
|
|
@@ -221,6 +273,15 @@ export type ParseFormDataOptions = {
|
|
|
221
273
|
* @example
|
|
222
274
|
* ```ts
|
|
223
275
|
* const formData = new FormData()
|
|
276
|
+
* formData.append('a[b]', 'foo')
|
|
277
|
+
* formData.append('a.c', 'bar')
|
|
278
|
+
* parseFormData(formData)
|
|
279
|
+
* // => {a: {b: 'foo', c: 'bar'}}
|
|
280
|
+
* ```
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* const formData = new FormData()
|
|
224
285
|
* formData.append('a[0]', 'foo')
|
|
225
286
|
* formData.append('a[1]', 'bar')
|
|
226
287
|
* parseFormData(formData)
|
|
@@ -240,7 +301,12 @@ export type ParseFormDataOptions = {
|
|
|
240
301
|
* ```ts
|
|
241
302
|
* const formData = new FormData()
|
|
242
303
|
* formData.append('a[0]', 'foo')
|
|
243
|
-
* parseFormData(formData, {
|
|
304
|
+
* parseFormData(formData, {
|
|
305
|
+
* transformEntry: ([path, value]) => ({
|
|
306
|
+
* path,
|
|
307
|
+
* value: typeof value === 'string' ? value + 'bar' : value,
|
|
308
|
+
* }),
|
|
309
|
+
* })
|
|
244
310
|
* // => {a: ['foobar']}
|
|
245
311
|
* ```
|
|
246
312
|
*
|
|
@@ -254,20 +320,49 @@ export type ParseFormDataOptions = {
|
|
|
254
320
|
* ```
|
|
255
321
|
*
|
|
256
322
|
* @param {Iterable<[string, string | File]>} formData - an iterator of an [`path`, `value`] tuple
|
|
257
|
-
* - `path` :=
|
|
258
|
-
*
|
|
323
|
+
* - `path` := an optional prefix (`+`, `-`, `&`), then path parts: keys after
|
|
324
|
+
* `.` or in brackets (`[key]`), and array parts `[]` or `[\d+]` (e.g.
|
|
325
|
+
* `+a[][1].b[c]`)
|
|
326
|
+
* - `value` := `string` or `File` (native or polyfilled `File` values are kept
|
|
327
|
+
* as they are)
|
|
259
328
|
* @param {ParseFormDataOptions} options - options for parsing the form data
|
|
260
329
|
* - `transformEntry` - a function to transform the path and the value before
|
|
261
330
|
* inserting the value at the path in the resulting object (default: `defaultTransform`)
|
|
262
331
|
* - `removeEmptyString` - if `true` removes all entries where the value is an empty string
|
|
332
|
+
* - `depth` - the maximum number of path parts in a path, counting every part
|
|
333
|
+
* (default: `Infinity`)
|
|
263
334
|
* @returns {JsonObject} the parsed JSON object
|
|
335
|
+
* @throws `TypeError` if `depth` isn't a positive integer or `Infinity`
|
|
336
|
+
* @throws `DepthLimitError` if a path has more path parts than `depth`
|
|
337
|
+
* - e.g. `a.b[0]` with `depth: 2` => `DepthLimitError('a.b[0]')`
|
|
264
338
|
* @throws `DuplicateKeyError` if
|
|
265
|
-
* -
|
|
266
|
-
*
|
|
339
|
+
* - two entries set a value at the same path
|
|
340
|
+
* - e.g. `a` and `a`, `a[0]` and `a[0]`, `a.b` and `a[b]`
|
|
341
|
+
* - an entry uses a path part as an object, array or value and another entry
|
|
342
|
+
* uses it differently
|
|
343
|
+
* - e.g. `a` and `a.b`, `a.b` and `a[]`, `a[]` and `a.b`
|
|
344
|
+
* - the path starts with an array part
|
|
345
|
+
* - e.g. `[0]` or `[]`
|
|
267
346
|
* @throws `MixedArrayError` if at a specific path part an unordered array is
|
|
268
347
|
* defined and at a later path part an ordered array is defined or vice versa
|
|
269
348
|
* - e.g. `a[0]` and `a[]`
|
|
270
349
|
* - e.g. `a[]` and `a[0]`
|
|
350
|
+
* @throws `ForbiddenKeyError` if an object path part is `__proto__`,
|
|
351
|
+
* `constructor` or `prototype`
|
|
352
|
+
* - e.g. `__proto__.polluted`
|
|
353
|
+
* @throws `InvalidPathError` if an array index is larger than `4294967294`
|
|
354
|
+
* - e.g. `a[4294967295]`
|
|
355
|
+
*
|
|
356
|
+
* Every error's `key` is the path after `transformEntry`, not the field name
|
|
357
|
+
* (e.g. `+a.b.c` with `depth: 2` => `DepthLimitError('a.b.c')`).
|
|
358
|
+
*
|
|
359
|
+
* An entry with any other object path part named like a property on
|
|
360
|
+
* `Object.prototype` (`toString`, `hasOwnProperty`, ...) is removed from the
|
|
361
|
+
* result. It's parsed like any other entry first, so it causes the same errors
|
|
362
|
+
* as an unused name: an error doesn't tell the client which properties exist
|
|
363
|
+
* on the server's `Object.prototype`.
|
|
364
|
+
* - e.g. `user.toString` => `{}`
|
|
365
|
+
* - e.g. `user[toString]` => `{}`
|
|
271
366
|
*/
|
|
272
|
-
export declare function parseFormData(formData: Iterable<[string, string | File]>, { removeEmptyString, transformEntry, }?: ParseFormDataOptions): JsonObject;
|
|
273
|
-
export {};
|
|
367
|
+
export declare function parseFormData(formData: Iterable<[string, string | File]>, { removeEmptyString, transformEntry, depth }?: ParseFormDataOptions): JsonObject;
|
|
368
|
+
export {};
|