Tsed: [BUG] Items of an array response of mongoose docs are not mapped properly

Created on 1 Nov 2020  路  13Comments  路  Source: tsedio/tsed

Information

  • Version: 6.1.5
  • Packages: @tsed/mongoose @tsed/schema @tsed/json-mapper

Hey guys. Not sure what exactly is wrong and where (which component), or maybe I'm missing something, so let me just describe the issue I've got.
Using the official tsed-example-mongoose, requesting /rest/calendars the result is like this

[
  {
    "_id": "5ce4ee471495836c5e2e4cb7",
    "__v": 0,
    "name": "Sexton Berg"
  }
  ...
]

Getting one document by id (/rest/calendars/5ce4ee471495836c5e2e4cb7) gives

{
  "id": "5ce4ee471495836c5e2e4cb7",
  "name": "Sexton Berg"
}

I would expect to have each element of an array of documents mapped similarly to a single document. Like this

[
  {
    "id": "5ce4ee471495836c5e2e4cb7",
    "name": "Sexton Berg"
  }
  ...
]

_id renamed to id, __v removed

Example

https://github.com/TypedProject/tsed-example-mongoose

Acceptance criteria

  • [ ] Able to get an array of mapped documents
bug released

Most helpful comment

Hello @freez10
The fix is release and I updated the mongoose project example. You can find some advanced concept to validate input data (eg: custom @CalendarId() decorator):

import {BodyParams, Controller, Delete, Get, PathParams, Post, Put} from "@tsed/common";
import {NotFound} from "@tsed/exceptions";
import {Description, Required, Returns, Status, Summary} from "@tsed/schema";
import {CalendarId} from "../../decorators/calendarId";
import {Calendar} from "../../models/calendars/Calendar";
import {CalendarsService} from "../../services/calendars/CalendarsService";
import {EventsCtrl} from "../events/EventsCtrl";

/**
 * Add @Controller annotation to declare your class as Router controller.
 * The first param is the global path for your controller.
 * The others params is the controller dependencies.
 *
 * In this case, EventsCtrl is a dependency of CalendarsCtrl.
 * All routes of EventsCtrl will be mounted on the `/calendars` path.
 */
@Controller({
  path: "/calendars",
  children: [EventsCtrl]
})
export class CalendarsCtrl {
  constructor(private calendarsService: CalendarsService) {
  }

  @Get("/:id")
  @Summary("Return a calendar from his ID")
  @Status(200, Calendar).Description("Success")
  async get(@PathParams("id") @CalendarId() id: string): Promise<Calendar> {
    const calendar = await this.calendarsService.find(id);

    if (calendar) {
      return calendar;
    }

    throw new NotFound("Calendar not found");
  }

  @Post("/")
  @Summary("Create a new Calendar")
  @Returns(201, Calendar).Description("Created")
  save(@Description("Calendar model")
       @BodyParams() @Required() calendar: Calendar) {
    return this.calendarsService.save(calendar);
  }

  @Put("/:id")
  @Summary("Update calendar information")
  @Returns(200, Calendar).Description("Success")
  async update(@PathParams("id") @CalendarId() id: string,
               @BodyParams() @Required() calendar: Calendar): Promise<Calendar> {
    calendar._id = id;

    return this.calendarsService.save(calendar);
  }

  @Delete("/:id")
  @Summary("Remove a calendar.")
  @Returns(204).Description("No content")
  async remove(@PathParams("id") @CalendarId() id: string): Promise<void> {
    await this.calendarsService.remove(id);
  }

  @Get("/")
  @Summary("Return all calendars")
  @Returns(200, Array).Of(Calendar)
  async getAllCalendars(): Promise<Calendar[]> {
    return this.calendarsService.query();
  }
}

See you
Romain

All 13 comments

PR #1042

:tada: This issue has been resolved in version 6.2.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

Hello @freez10
The fix is release and I updated the mongoose project example. You can find some advanced concept to validate input data (eg: custom @CalendarId() decorator):

import {BodyParams, Controller, Delete, Get, PathParams, Post, Put} from "@tsed/common";
import {NotFound} from "@tsed/exceptions";
import {Description, Required, Returns, Status, Summary} from "@tsed/schema";
import {CalendarId} from "../../decorators/calendarId";
import {Calendar} from "../../models/calendars/Calendar";
import {CalendarsService} from "../../services/calendars/CalendarsService";
import {EventsCtrl} from "../events/EventsCtrl";

/**
 * Add @Controller annotation to declare your class as Router controller.
 * The first param is the global path for your controller.
 * The others params is the controller dependencies.
 *
 * In this case, EventsCtrl is a dependency of CalendarsCtrl.
 * All routes of EventsCtrl will be mounted on the `/calendars` path.
 */
@Controller({
  path: "/calendars",
  children: [EventsCtrl]
})
export class CalendarsCtrl {
  constructor(private calendarsService: CalendarsService) {
  }

  @Get("/:id")
  @Summary("Return a calendar from his ID")
  @Status(200, Calendar).Description("Success")
  async get(@PathParams("id") @CalendarId() id: string): Promise<Calendar> {
    const calendar = await this.calendarsService.find(id);

    if (calendar) {
      return calendar;
    }

    throw new NotFound("Calendar not found");
  }

  @Post("/")
  @Summary("Create a new Calendar")
  @Returns(201, Calendar).Description("Created")
  save(@Description("Calendar model")
       @BodyParams() @Required() calendar: Calendar) {
    return this.calendarsService.save(calendar);
  }

  @Put("/:id")
  @Summary("Update calendar information")
  @Returns(200, Calendar).Description("Success")
  async update(@PathParams("id") @CalendarId() id: string,
               @BodyParams() @Required() calendar: Calendar): Promise<Calendar> {
    calendar._id = id;

    return this.calendarsService.save(calendar);
  }

  @Delete("/:id")
  @Summary("Remove a calendar.")
  @Returns(204).Description("No content")
  async remove(@PathParams("id") @CalendarId() id: string): Promise<void> {
    await this.calendarsService.remove(id);
  }

  @Get("/")
  @Summary("Return all calendars")
  @Returns(200, Array).Of(Calendar)
  async getAllCalendars(): Promise<Calendar[]> {
    return this.calendarsService.query();
  }
}

See you
Romain

Hey @Romakita it looks like it is broken again in 6.8.1
image

Have you added @ObjectId('id') on this prop?

Have you added @ObjectId('id') on this prop?

@Romakita sure. Just downgraded to 6.2.0 and there is how it works now
image

`
export class FileModel {
@Required()
@ObjectID('id')
_id: string

@Default('root')
@Required()
parent: string

@Property()
@Required()
ownerId: string
//..
}
`

Damned...

@freez10 Did you use @Returns(200, FileModel) on controller?

@Returns(200, Array).Of(FileModel)

 @Get('/')
  @Returns(200).Type(Array).Of(FileModel)
  @Returns(500, ErrorModel)
  async list (@Req('account') user: UserType, @QueryParams('folder') folder: string = 'root'): Promise<FileModel[]> {
    return await this.filesModel
      .find({ ownerId: user.id, parent: folder })
      .sort({ createdAt: 'asc' })
      .exec()
  }

Tried @Returns(200, Array).Of(FileModel) - same result

:tada: This issue has been resolved in version 6.9.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

@freez10 Fixed :)

@Romakita Thank you! You are the best :)

Was this page helpful?
0 / 5 - 0 ratings