Skip to content

Commit

Permalink
Merge pull request #15 from Balastrong/support-bracket-notation
Browse files Browse the repository at this point in the history
feat: support bracket notation for arrays
  • Loading branch information
fabian-hiller authored Aug 20, 2024
2 parents a076e55 + e0fbdd5 commit db319f4
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to the library will be documented in this file.

## vX.X.X (Month DD, YYYY)

- Add support for array bracket notation (pull request #15)

## v0.7.5 (June 01, 2024)

- Republish previous version because build step was forgotten
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import { decode } from 'https://deno.land/x/decode_formdata/mod.ts';

`FormData` stores the names of your fields and their values. However, there is a problem. Only strings and files are accepted as values, but complex forms can contain booleans, strings and dates. This leads to the fact that the boolean value `true` must be mapped with `"on"` and `false` values are simply ignored. Numbers and dates are also converted to strings.

Another problem are objects and arrays, which are usually mapped using dot notation. For example, the input field `<input name="todos.0.label" />` should map to the object `{ todos: [{ label: "" }] }`. By telling `decode` where arrays, booleans, dates, files, and numbers are located, the function can decode your `FormData` back into a complex JavaScript object.
Another problem are objects and arrays, which are usually mapped using dot and bracket notation. For example, the input field `<input name="todos.0.label" />` should map to the object `{ todos: [{ label: "" }] }`. By telling `decode` where arrays, booleans, dates, files, and numbers are located, the function can decode your `FormData` back into a complex JavaScript object.

> Both dot and bracket notation are supported for arrays.
Consider the following form to add a new product to an online store:

Expand Down Expand Up @@ -174,3 +176,7 @@ Find a bug or have an idea how to improve the library? Please fill out an [issue
## License

This project is available free of charge and licensed under the [MIT license](https://github.com/fabian-hiller/decode-formdata/blob/main/LICENSE.md).

## Note

Both dot and bracket notation are supported for arrays.
43 changes: 40 additions & 3 deletions src/decode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('decode', () => {
expect(decode(formData, { files: ['file'] })).toEqual({ file });
});

test('should decode indexed arrays', () => {
test('should decode indexed arrays with dot notation', () => {
const formData = new FormData();
formData.append('array.0', 'index_0');
formData.append('array.1', 'index_1');
Expand All @@ -74,6 +74,16 @@ describe('decode', () => {
});
});

test('should decode indexed arrays with bracket notation', () => {
const formData = new FormData();
formData.append('array[0]', 'index_0');
formData.append('array[1]', 'index_1');
formData.append('array[2]', 'index_2');
expect(decode(formData, { arrays: ['array'] })).toEqual({
array: ['index_0', 'index_1', 'index_2'],
});
});

test('should decode non-indexed arrays', () => {
const formData = new FormData();
formData.append('array', 'index_0');
Expand All @@ -88,7 +98,7 @@ describe('decode', () => {
});
});

test('should decode numbers in array', () => {
test('should decode numbers in array with dot notation', () => {
const formData = new FormData();
formData.append('array.0', '111');
formData.append('array.1', '222');
Expand All @@ -100,6 +110,18 @@ describe('decode', () => {
});
});

test('should decode numbers in array with bracket notation', () => {
const formData = new FormData();
formData.append('array[0]', '111');
formData.append('array[1]', '222');
formData.append('array[2]', '333');
expect(
decode(formData, { arrays: ['array'], numbers: ['array[$]'] })
).toEqual({
array: [111, 222, 333],
});
});

test('should decode objects', () => {
const formData = new FormData();
formData.append('nested.string', 'hello');
Expand All @@ -108,7 +130,7 @@ describe('decode', () => {
});
});

test('should decode nested arrays', () => {
test('should decode nested arrays with dot notation', () => {
const formData = new FormData();
formData.append('nested.0.array.0', 'index_0');
formData.append('nested.0.array.1', 'index_1');
Expand All @@ -123,6 +145,21 @@ describe('decode', () => {
});
});

test('should decode nested arrays with bracket notation', () => {
const formData = new FormData();
formData.append('nested[0].array[0]', 'index_0');
formData.append('nested[0].array[1]', 'index_1');
formData.append('nested[0].array[2]', 'index_2');
expect(
decode(formData, {
arrays: ['nested[$].array', 'empty.array'],
})
).toEqual({
nested: [{ array: ['index_0', 'index_1', 'index_2'] }],
empty: { array: [] },
});
});

test('should transform value', () => {
const formData = new FormData();
formData.append('string', 'hello');
Expand Down
24 changes: 22 additions & 2 deletions src/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,39 @@ export function decode<
const [info, transform] =
typeof arg2 === 'function' ? [undefined, arg2] : [arg2, arg3];

// Normalize info arrays to dot notation
if (info) {
for (const key of [
'arrays',
'booleans',
'dates',
'files',
'numbers',
] as const) {
if (info[key]?.length) {
info[key] = info[key]!.map((templateName) =>
templateName.replace(/\[\$\]/g, '.$')
);
}
}
}

// Create empty values object
const values: any = {};

// Add each form entry to values
for (const [path, input] of formData.entries()) {
// Normalize path to dot notation
const normlizedPath = path.replace(/\[(\d+)\]/g, '.$1');

// Create template name and keys
const templateName = path
const templateName = normlizedPath
.replace(/\.\d+\./g, '.$.')
.replace(/\.\d+$/, '.$');
const templateKeys = templateName.split('.');

// Add value of current field to values
path.split('.').reduce((object, key, index, keys) => {
normlizedPath.split('.').reduce((object, key, index, keys) => {
// If it is not last index, return array or object
if (index < keys.length - 1) {
// If array or object already exists, return it
Expand Down

0 comments on commit db319f4

Please sign in to comment.