Skip to content

Commit 89acf02

Browse files
committed
feat: support bracket notation for arrays
This PR resolves #14 I just launched Copilot Workspace and tweaked the output a bit, if this PR doesn't make any sense feel free to discard it but... tests are passing :D --- Add support for bracket notation for arrays in form data decoding. * **README.md** - Update examples to include bracket notation for arrays. - Add a note explaining that both dot and bracket notation are supported. * **src/decode.test.ts** - Add tests for decoding arrays using bracket notation. - Ensure existing tests for dot notation are not affected. * **src/decode.ts** - Update the `decode` function to handle bracket notation for arrays. - Modify the logic to parse and decode form data entries with bracket notation. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/fabian-hiller/decode-formdata?shareId=XXXX-XXXX-XXXX-XXXX).
1 parent a076e55 commit 89acf02

File tree

3 files changed

+103
-57
lines changed

3 files changed

+103
-57
lines changed

README.md

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,17 @@ Consider the following form to add a new product to an online store:
4646
<input name="active" type="checkbox" />
4747

4848
<!-- Tags -->
49-
<input name="tags.0" type="text" />
50-
<input name="tags.1" type="text" />
51-
<input name="tags.2" type="text" />
49+
<input name="tags[0]" type="text" />
50+
<input name="tags[1]" type="text" />
51+
<input name="tags[2]" type="text" />
5252

5353
<!-- Images -->
54-
<input name="images.0.title" type="text" />
55-
<input name="images.0.created" type="date" />
56-
<input name="images.0.file" type="file" />
57-
<input name="images.1.title" type="text" />
58-
<input name="images.1.created" type="date" />
59-
<input name="images.1.file" type="file" />
54+
<input name="images[0].title" type="text" />
55+
<input name="images[0].created" type="date" />
56+
<input name="images[0].file" type="file" />
57+
<input name="images[1].title" type="text" />
58+
<input name="images[1].created" type="date" />
59+
<input name="images[1].file" type="file" />
6060
</form>
6161
```
6262

@@ -68,15 +68,15 @@ const formEntries = [
6868
['price', '0.89'],
6969
['created', '2023-10-09'],
7070
['active', 'on'],
71-
['tags.0', 'fruit'],
72-
['tags.1', 'healthy'],
73-
['tags.2', 'sweet'],
74-
['images.0.title', 'Close up of an apple'],
75-
['images.0.created', '2023-08-24'],
76-
['images.0.file', Blob],
77-
['images.1.title', 'Our fruit fields at Lake Constance'],
78-
['images.1.created', '2023-08-12'],
79-
['images.1.file', Blob],
71+
['tags[0]', 'fruit'],
72+
['tags[1]', 'healthy'],
73+
['tags[2]', 'sweet'],
74+
['images[0].title', 'Close up of an apple'],
75+
['images[0].created', '2023-08-24'],
76+
['images[0].file', Blob],
77+
['images[1].title', 'Our fruit fields at Lake Constance'],
78+
['images[1].created', '2023-08-12'],
79+
['images[1].file', Blob],
8080
];
8181
```
8282

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

176176
This project is available free of charge and licensed under the [MIT license](https://github.com/fabian-hiller/decode-formdata/blob/main/LICENSE.md).
177+
178+
## Note
179+
180+
Both dot and bracket notation are supported for arrays.

src/decode.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ describe('decode', () => {
7474
});
7575
});
7676

77+
test('should decode indexed arrays with bracket notation', () => {
78+
const formData = new FormData();
79+
formData.append('array[0]', 'index_0');
80+
formData.append('array[1]', 'index_1');
81+
formData.append('array[2]', 'index_2');
82+
expect(decode(formData, { arrays: ['array'] })).toEqual({
83+
array: ['index_0', 'index_1', 'index_2'],
84+
});
85+
});
86+
7787
test('should decode non-indexed arrays', () => {
7888
const formData = new FormData();
7989
formData.append('array', 'index_0');
@@ -100,6 +110,18 @@ describe('decode', () => {
100110
});
101111
});
102112

113+
test('should decode numbers in array with bracket notation', () => {
114+
const formData = new FormData();
115+
formData.append('array[0]', '111');
116+
formData.append('array[1]', '222');
117+
formData.append('array[2]', '333');
118+
expect(
119+
decode(formData, { arrays: ['array'], numbers: ['array.$'] })
120+
).toEqual({
121+
array: [111, 222, 333],
122+
});
123+
});
124+
103125
test('should decode objects', () => {
104126
const formData = new FormData();
105127
formData.append('nested.string', 'hello');
@@ -123,6 +145,21 @@ describe('decode', () => {
123145
});
124146
});
125147

148+
test('should decode nested arrays with bracket notation', () => {
149+
const formData = new FormData();
150+
formData.append('nested[0].array[0]', 'index_0');
151+
formData.append('nested[0].array[1]', 'index_1');
152+
formData.append('nested[0].array[2]', 'index_2');
153+
expect(
154+
decode(formData, {
155+
arrays: ['nested.$.array', 'empty.array'],
156+
})
157+
).toEqual({
158+
nested: [{ array: ['index_0', 'index_1', 'index_2'] }],
159+
empty: { array: [] },
160+
});
161+
});
162+
126163
test('should transform value', () => {
127164
const formData = new FormData();
128165
formData.append('string', 'hello');

src/decode.ts

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -51,55 +51,60 @@ export function decode<
5151
// Create template name and keys
5252
const templateName = path
5353
.replace(/\.\d+\./g, '.$.')
54-
.replace(/\.\d+$/, '.$');
54+
.replace(/\.\d+$/, '.$')
55+
.replace(/\[\d+\]/g, '.$&')
56+
.replace(/\[(\d+)\]/g, '$');
5557
const templateKeys = templateName.split('.');
5658

5759
// Add value of current field to values
58-
path.split('.').reduce((object, key, index, keys) => {
59-
// If it is not last index, return array or object
60-
if (index < keys.length - 1) {
61-
// If array or object already exists, return it
62-
if (object[key]) {
63-
return object[key];
64-
}
65-
66-
// Otherwise, check if value is an array
67-
const isArray =
68-
index < keys.length - 2
69-
? templateKeys[index + 1] === '$'
70-
: info?.arrays?.includes(templateKeys.slice(0, -1).join('.'));
60+
path
61+
.replace(/\[(\d+)\]/g, '.$1')
62+
.split('.')
63+
.reduce((object, key, index, keys) => {
64+
// If it is not last index, return array or object
65+
if (index < keys.length - 1) {
66+
// If array or object already exists, return it
67+
if (object[key]) {
68+
return object[key];
69+
}
7170

72-
// Add and return empty array or object
73-
return (object[key] = isArray ? [] : {});
74-
}
71+
// Otherwise, check if value is an array
72+
const isArray =
73+
index < keys.length - 2
74+
? templateKeys[index + 1] === '$'
75+
: info?.arrays?.includes(templateKeys.slice(0, -1).join('.'));
7576

76-
// Otherwise, if it is not an empty file, add value
77-
if (
78-
!info?.files?.includes(templateName) ||
79-
(input && (typeof input === 'string' || input.size))
80-
) {
81-
// Get field value
82-
let output = getFieldValue(info, templateName, input);
83-
84-
// Transform value if necessary
85-
if (transform) {
86-
output = transform({ path, input, output });
77+
// Add and return empty array or object
78+
return (object[key] = isArray ? [] : {});
8779
}
8880

89-
// If it is an non-indexed array, add value to array
90-
if (info?.arrays?.includes(templateName)) {
91-
if (object[key]) {
92-
object[key].push(output);
93-
} else {
94-
object[key] = [output];
81+
// Otherwise, if it is not an empty file, add value
82+
if (
83+
!info?.files?.includes(templateName) ||
84+
(input && (typeof input === 'string' || input.size))
85+
) {
86+
// Get field value
87+
let output = getFieldValue(info, templateName, input);
88+
89+
// Transform value if necessary
90+
if (transform) {
91+
output = transform({ path, input, output });
9592
}
9693

97-
// Otherwise, add value directly to key
98-
} else {
99-
object[key] = output;
94+
// If it is an non-indexed array, add value to array
95+
if (info?.arrays?.includes(templateName)) {
96+
if (object[key]) {
97+
object[key].push(output);
98+
} else {
99+
object[key] = [output];
100+
}
101+
102+
// Otherwise, add value directly to key
103+
} else {
104+
object[key] = output;
105+
}
100106
}
101-
}
102-
}, values);
107+
}, values);
103108
}
104109

105110
// Supplement empty arrays if necessary

0 commit comments

Comments
 (0)