Skip to content

Commit

Permalink
fix: nested repeated array syntax (#43)
Browse files Browse the repository at this point in the history
Fixes #42

Essentially we had two bugs 👀

Bug 1: if you have repeat syntax when using bracket-notation, but only
had one key, we would _not_ create it as a string.

For example:

```ts
parse('foo[]=1', opts); // {foo: 1} - wrong!

parse('foo[]=1&foo[]=2', opts); // {foo: [1, 2]} - right!
```

Bug 2: if you have repeat syntax following an index-nested member, we
would not parse it correctly.

For example:

```ts
parse('foo[bar][]=1', opts); // {foo: {bar: 1}} - wrong!

// expected: {foo: {bar: [1]}}
```

These should both now be fixed.
  • Loading branch information
43081j authored Jun 25, 2024
1 parent 6b05645 commit 5d0a39f
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 24 deletions.
58 changes: 34 additions & 24 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export function parse(input: string, options?: ParseOptions): ParsedQuery {
let hasBothKeyValuePair = false;
let c = 0;
let arrayRepeatBracketIndex = -1;
let prevIndex = -1;
let prevChar = -1;

// Have a boundary of input.length + 1 to access last pair inside the loop.
for (let i = 0; i < inputLength + 1; i++) {
Expand Down Expand Up @@ -150,7 +152,11 @@ export function parse(input: string, options?: ParseOptions): ParsedQuery {
if (arrayRepeat) {
const currentValue = currentObj[currentKey];
if (currentValue === undefined) {
currentObj[currentKey] = newValue;
if (arrayRepeatBracketIndex > -1) {
currentObj[currentKey] = [newValue];
} else {
currentObj[currentKey] = newValue;
}
}
// Optimization: value.pop is faster than Array.isArray(value)
else if ((currentValue as unknown[]).pop) {
Expand Down Expand Up @@ -180,8 +186,7 @@ export function parse(input: string, options?: ParseOptions): ParsedQuery {
// Check ']'
else if (c === 93) {
if (arrayRepeat && arrayRepeatSyntax === 'bracket') {
const prevIndex = i - 1;
if (input.charCodeAt(prevIndex) === 91) {
if (prevChar === 91) {
arrayRepeatBracketIndex = prevIndex;
}
}
Expand All @@ -191,29 +196,31 @@ export function parse(input: string, options?: ParseOptions): ParsedQuery {
(nestingSyntax === 'index' || isJsNestingSyntax) &&
equalityIndex <= startingIndex
) {
keyChunk = computeKeySlice(
input,
keySeparatorIndex + 1,
i,
keyHasPlus,
shouldDecodeKey
);

currentKey = keyDeserializer(keyChunk);
if (lastKey !== undefined) {
currentObj = getDeepObject(
currentObj,
lastKey,
currentKey,
undefined,
isJsNestingSyntax
if (keySeparatorIndex !== prevIndex) {
keyChunk = computeKeySlice(
input,
keySeparatorIndex + 1,
i,
keyHasPlus,
shouldDecodeKey
);

currentKey = keyDeserializer(keyChunk);
if (lastKey !== undefined) {
currentObj = getDeepObject(
currentObj,
lastKey,
currentKey,
undefined,
isJsNestingSyntax
);
}
lastKey = currentKey;
keyHasPlus = false;
shouldDecodeKey = false;
}
lastKey = currentKey;

keySeparatorIndex = i;
keyHasPlus = false;
shouldDecodeKey = false;
keyIsIndex = true;
keyIsDot = false;
}
Expand All @@ -225,7 +232,7 @@ export function parse(input: string, options?: ParseOptions): ParsedQuery {
(nestingSyntax === 'dot' || isJsNestingSyntax) &&
equalityIndex <= startingIndex
) {
if (keySeparatorIndex !== i - 1) {
if (keySeparatorIndex !== prevIndex) {
keyChunk = computeKeySlice(
input,
keySeparatorIndex + 1,
Expand Down Expand Up @@ -261,7 +268,7 @@ export function parse(input: string, options?: ParseOptions): ParsedQuery {
(nestingSyntax === 'index' || isJsNestingSyntax) &&
equalityIndex <= startingIndex
) {
if (keySeparatorIndex !== i - 1) {
if (keySeparatorIndex !== prevIndex) {
keyChunk = computeKeySlice(
input,
keySeparatorIndex + 1,
Expand Down Expand Up @@ -316,6 +323,9 @@ export function parse(input: string, options?: ParseOptions): ParsedQuery {
shouldDecodeKey = true;
}
}

prevIndex = i;
prevChar = c;
}

return result;
Expand Down
110 changes: 110 additions & 0 deletions src/test/test-cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,23 @@ export const testCases: TestCase[] = [
output: {foo: ['x', 'y']},
options: {arrayRepeat: true, arrayRepeatSyntax: 'bracket'}
},
{
input: 'foo[]=x',
stringifyOutput: 'foo%5B%5D=x',
output: {foo: ['x']},
options: {arrayRepeat: true, arrayRepeatSyntax: 'bracket'}
},
// Array syntax: repeat
{
input: 'foo=x&foo=y',
output: {foo: ['x', 'y']},
options: {arrayRepeat: true, arrayRepeatSyntax: 'repeat'}
},
{
input: 'foo=x',
output: {foo: 'x'},
options: {arrayRepeat: true, arrayRepeatSyntax: 'repeat'}
},
{
input: 'foo=x&foo=y&foo=z',
output: {foo: ['x', 'y', 'z']},
Expand Down Expand Up @@ -200,6 +211,39 @@ export const testCases: TestCase[] = [
output: {foo: {bar: 'trzy'}},
options: {nesting: true, nestingSyntax: 'index'}
},
{
input: 'foo[0]=trzy&foo[]=cztery',
stringifyOutput: 'foo%5B%5D=trzy&foo%5B%5D=cztery',
output: {foo: ['trzy', 'cztery']},
options: {
nesting: true,
nestingSyntax: 'index',
arrayRepeat: true,
arrayRepeatSyntax: 'bracket'
}
},
{
input: 'foo[bar][]=baz',
stringifyOutput: 'foo%5Bbar%5D%5B%5D=baz',
output: {foo: {bar: ['baz']}},
options: {
nesting: true,
nestingSyntax: 'index',
arrayRepeat: true,
arrayRepeatSyntax: 'bracket'
}
},
{
input: 'foo[bar][]=x&foo[bar][]=y',
stringifyOutput: 'foo%5Bbar%5D%5B%5D=x&foo%5Bbar%5D%5B%5D=y',
output: {foo: {bar: ['x', 'y']}},
options: {
nesting: true,
nestingSyntax: 'index',
arrayRepeat: true,
arrayRepeatSyntax: 'bracket'
}
},

// Nesting syntax: dot
{
Expand Down Expand Up @@ -227,6 +271,28 @@ export const testCases: TestCase[] = [
output: {'foo.bar': 'x', 'foo.baz': 'y'},
options: {nesting: true, nestingSyntax: 'index'}
},
{
input: 'foo.bar[]=baz',
stringifyOutput: 'foo.bar%5B%5D=baz',
output: {foo: {bar: ['baz']}},
options: {
nesting: true,
nestingSyntax: 'dot',
arrayRepeat: true,
arrayRepeatSyntax: 'bracket'
}
},
{
input: 'foo.bar[]=x&foo.bar[]=y',
stringifyOutput: 'foo.bar%5B%5D=x&foo.bar%5B%5D=y',
output: {foo: {bar: ['x', 'y']}},
options: {
nesting: true,
nestingSyntax: 'dot',
arrayRepeat: true,
arrayRepeatSyntax: 'bracket'
}
},

// Nesting syntax: js
{
Expand Down Expand Up @@ -262,6 +328,50 @@ export const testCases: TestCase[] = [
output: {foo: {bar: {x: 'x', y: 'y'}}},
options: {nesting: true, nestingSyntax: 'js'}
},
{
input: 'foo.bar[]=x',
stringifyOutput: 'foo.bar%5B%5D=x',
output: {foo: {bar: ['x']}},
options: {
nesting: true,
nestingSyntax: 'js',
arrayRepeat: true,
arrayRepeatSyntax: 'bracket'
}
},
{
input: 'foo[0][]=x',
stringifyOutput: 'foo%5B%5D%5B%5D=x',
output: {foo: [['x']]},
options: {
nesting: true,
nestingSyntax: 'js',
arrayRepeat: true,
arrayRepeatSyntax: 'bracket'
}
},
{
input: 'foo.bar[]=x&foo.bar[]=y',
stringifyOutput: 'foo.bar%5B%5D=x&foo.bar%5B%5D=y',
output: {foo: {bar: ['x', 'y']}},
options: {
nesting: true,
nestingSyntax: 'js',
arrayRepeat: true,
arrayRepeatSyntax: 'bracket'
}
},
{
input: 'foo[0][]=x&foo[0][]=y',
stringifyOutput: 'foo%5B%5D%5B%5D=x&foo%5B%5D%5B%5D=y',
output: {foo: [['x', 'y']]},
options: {
nesting: true,
nestingSyntax: 'js',
arrayRepeat: true,
arrayRepeatSyntax: 'bracket'
}
},

// Sparse array with nestinh
{
Expand Down

0 comments on commit 5d0a39f

Please sign in to comment.