Skip to content

Commit 08e7aa0

Browse files
authored
Merge pull request #677 from BenjD90/fix-query-params-parsing
feat: add cast array to expected array query params
2 parents f163c34 + 8e211f3 commit 08e7aa0

File tree

7 files changed

+248
-11
lines changed

7 files changed

+248
-11
lines changed

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,28 @@ getUsers(@QueryParam("limit") limit: number) {
375375
}
376376
```
377377

378+
You can use `isArray` option to get a query param array. This will cast the query param :
379+
380+
```typescript
381+
@Get("/users/by-multiple-ids")
382+
getUsers(@QueryParam("ids", { isArray: true}) ids: string[]) {
383+
}
384+
```
385+
386+
`GET /users/by-multiple-ids?ids=a``ids = ['a']`
387+
`GET /users/by-multiple-ids?ids=a&ids=b``ids = ['a', 'b']`
388+
389+
You can combine use `isArray` option with `type` option to get a query param array of one type. This will cast the query param :
390+
391+
```typescript
392+
@Get("/users/by-multiple-ids")
393+
getUsers(@QueryParam("ids", { isArray: true, type: Number}) ids: number[]) {
394+
}
395+
```
396+
397+
`GET /users/by-multiple-ids?ids=1``ids = [1]`
398+
`GET /users/by-multiple-ids?ids=1&ids=3.5``ids = [1, 3.5]`
399+
378400
If you want to inject all query parameters use `@QueryParams()` decorator.
379401
The biggest benefit of this approach is that you can perform validation of the params.
380402

@@ -399,12 +421,17 @@ class GetUsersQuery {
399421
@IsBoolean()
400422
isActive: boolean;
401423

424+
@IsArray()
425+
@IsNumber(undefined, { each: true })
426+
@Type(() => Number)
427+
ids: number[];
402428
}
403429

404430
@Get("/users")
405431
getUsers(@QueryParams() query: GetUsersQuery) {
406432
// here you can access query.role, query.limit
407433
// and others valid query parameters
434+
// query.ids will be an array, of numbers, even with one element
408435
}
409436
```
410437

@@ -1532,7 +1559,7 @@ export class QuestionController {
15321559

15331560
| Signature | Example | Description |
15341561
| ---------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
1535-
| `@Authorized(roles?: string\|string[])` | `@Authorized("SUPER_ADMIN")` get() | Checks if user is authorized and has given roles on a given route. `authorizationChecker` should be defined in routing-controllers options. | |
1562+
| `@Authorized(roles?: string\|string[])` | `@Authorized("SUPER_ADMIN")` get() | Checks if user is authorized and has given roles on a given route. `authorizationChecker` should be defined in routing-controllers options. |
15361563
| `@CurrentUser(options?: { required?: boolean })` | get(@CurrentUser({ required: true }) user: User) | Injects currently authorized user. `currentUserChecker` should be defined in routing-controllers options. |
15371564
| `@Header(headerName: string, headerValue: string)` | `@Header("Cache-Control", "private")` get() | Allows to explicitly set any HTTP header returned in the response. |
15381565
| `@ContentType(contentType: string)` | `@ContentType("text/csv")` get() | Allows to explicitly set HTTP Content-Type returned in the response. |

src/ActionParameterHandler.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,13 @@ export class ActionParameterHandler<T extends BaseDriver> {
133133
case 'string':
134134
case 'boolean':
135135
case 'date':
136-
return this.normalizeStringValue(value, param.name, param.targetName);
136+
const normalizedValue = this.normalizeStringValue(value, param.name, param.targetName);
137+
return param.isArray ? [normalizedValue] : normalizedValue;
138+
case 'array':
139+
return [value];
137140
}
141+
} else if (Array.isArray(value)) {
142+
return value.map(v => this.normalizeStringValue(v, param.name, param.targetName));
138143
}
139144

140145
// if target type is not primitive, transform and validate it
@@ -191,10 +196,14 @@ export class ActionParameterHandler<T extends BaseDriver> {
191196
*/
192197
protected parseValue(value: any, paramMetadata: ParamMetadata): any {
193198
if (typeof value === 'string') {
194-
try {
195-
return JSON.parse(value);
196-
} catch (error) {
197-
throw new ParameterParseJsonError(paramMetadata.name, value);
199+
if (['queries', 'query'].includes(paramMetadata.type) && paramMetadata.targetName === 'array') {
200+
return [value];
201+
} else {
202+
try {
203+
return JSON.parse(value);
204+
} catch (error) {
205+
throw new ParameterParseJsonError(paramMetadata.name, value);
206+
}
198207
}
199208
}
200209
return value;

src/decorator-options/ParamOptions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,9 @@ export interface ParamOptions {
3232
* Explicitly set type which should be used for param to perform transformation.
3333
*/
3434
type?: any;
35+
36+
/**
37+
* Force value to be cast as an array.
38+
*/
39+
isArray?: boolean;
3540
}

src/decorator/QueryParam.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function QueryParam(name: string, options?: ParamOptions): Function {
1818
classTransform: options ? options.transform : undefined,
1919
explicitType: options ? options.type : undefined,
2020
validate: options ? options.validate : undefined,
21+
isArray: options?.isArray ?? false,
2122
});
2223
};
2324
}

src/metadata/ParamMetadata.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ export class ParamMetadata {
7878
*/
7979
transform: (action: Action, value?: any) => Promise<any> | any;
8080

81+
/**
82+
* If true, string values are cast to arrays
83+
*/
84+
isArray?: boolean;
85+
8186
/**
8287
* Additional parameter options.
8388
* For example it can be uploader middleware options or body-parser middleware options.
@@ -113,6 +118,7 @@ export class ParamMetadata {
113118
this.transform = args.transform;
114119
this.classTransform = args.classTransform;
115120
this.validate = args.validate;
121+
this.isArray = args.isArray;
116122

117123
if (args.explicitType) {
118124
this.targetType = args.explicitType;

src/metadata/args/ParamMetadataArgs.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,9 @@ export interface ParamMetadataArgs {
6666
* Explicitly set type which should be used for Body to perform transformation.
6767
*/
6868
explicitType?: any;
69+
70+
/**
71+
* Explicitly tell that the QueryParam is an array to force routing-controller to cast it
72+
*/
73+
isArray?: boolean;
6974
}

0 commit comments

Comments
 (0)