typescript 45 lines · 8 steps

Parsing bracketed form field names into nested objects

Turn HTML form fields like user[roles][] into a nested object by parsing their names and building the structure on the fly.

Explained by highlit
1type NestedValue = string | NestedValue[] | { [key: string]: NestedValue };
2 
3function parseFieldPath(name: string): string[] {
4 const match = name.match(/^([^\[\]]+)((?:\[[^\[\]]*\])*)$/);
5 if (!match) return [name];
6 const [, head, rest] = match;
7 const keys = [head];
8 for (const segment of rest.matchAll(/\[([^\[\]]*)\]/g)) {
9 keys.push(segment[1]);
10 }
11 return keys;
12}
13 
14function assign(target: Record<string, NestedValue>, keys: string[], value: string): void {
15 let cursor: any = target;
16 for (let i = 0; i < keys.length - 1; i++) {
17 const key = keys[i];
18 const nextKey = keys[i + 1];
19 const isArray = nextKey === '' || /^\d+$/.test(nextKey);
20 if (cursor[key] == null) {
21 cursor[key] = isArray ? [] : {};
22 }
23 cursor = cursor[key];
24 }
25 
26 const last = keys[keys.length - 1];
27 if (last === '' && Array.isArray(cursor)) {
28 cursor.push(value);
29 } else {
30 cursor[last] = value;
31 }
32}
33 
34export function serializeForm(form: HTMLFormElement): Record<string, NestedValue> {
35 const result: Record<string, NestedValue> = {};
36 const data = new FormData(form);
37 
38 for (const [name, raw] of data.entries()) {
39 if (typeof raw !== 'string') continue;
40 const keys = parseFieldPath(name);
41 assign(result, keys, raw);
42 }
43 
44 return result;
45}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A recursive type like NestedValue lets you describe arbitrarily deep mixes of objects and arrays in one definition.
  2. 2Peeking at the next key decides whether to create an array or object, so structure emerges from the path shape itself.
  3. 3Splitting name-parsing from value-assignment keeps each function small and independently testable.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Parsing bracketed form field names into nested objects — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code