typeorm / typeorm

ORM for TypeScript and JavaScript. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, SAP Hana, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.
http://typeorm.io
MIT License
34.03k stars 6.27k forks source link

TypeORM Mongo ObjectIdColumn JSON output #2011

Closed Kalhama closed 6 years ago

Kalhama commented 6 years ago

Issue type:

[x] question [ ] bug report [ ] feature request [ ] documentation issue

Database system/driver:

[ ] cordova [x] mongodb [ ] mssql [ ] mysql / mariadb [ ] oracle [ ] postgres [ ] sqlite [ ] sqljs [ ] react-native

TypeORM version:

[ ] latest [ ] @next [x] 0.2.0

Steps to reproduce or a small repository showing the problem:

I tried using TypeORM with Mongo as a part of a REST api. I created a simple endpoint which should return the data as a JSON. I however do get quite a weird results:

Please note that I'm using routing-controllers for handling the endpoint

Controller

import {Body, Delete, Get, JsonController, NotFoundError, Param, Post, Put} from 'routing-controllers'
import { createConnection } from 'typeorm'
import { User } from './entity/User'

@JsonController()
export class Routes {

  @Get('/')
  public async index() {
    try {
      const conn = await createConnection()

      const user = new User()
      user.username = 'test'
      await conn.mongoManager.save(user)

      return await conn.mongoManager.find(User)
    } catch (e) {
      console.log(e)
    }
  }
}

Entity:

import {Column, Entity, ObjectID, ObjectIdColumn} from 'typeorm'

@Entity()
export class User {
  @ObjectIdColumn()
  id: ObjectID

  @Column()
  username: string
}

GET /

[
    {
        "id": {
            "_bsontype": "ObjectID",
            "id": {
                "0": 90,
                "1": 222,
                "2": 23,
                "3": 59,
                "4": 109,
                "5": 180,
                "6": 239,
                "7": 212,
                "8": 20,
                "9": 157,
                "10": 13,
                "11": 1
            }
        },
        "username": "test"
    },
    ...
]

I have a feeling this is due a way underlying MongoDB works under TypeORM, but I couldn't find anything by Googling, I assume this should be quite a conventional use case, but for some reason I am unable to get it work

pleerock commented 6 years ago

this issue is not related to typeorm. ObjectID is an object and routing-controller tries to transform this object (as any other) to json. Solution here is to tweak serialization process and convert object id into something else, for example a string.

Kalhama commented 6 years ago

Thanks a lot for your response! Serialization was a good keyword and finally I found a sleek solution based on which had already been discussed in class-transformer repo: https://github.com/typestack/class-transformer/issues/87

Solution was to edit entity file like this:

import {Transform} from 'class-transformer'
import {Column, Entity, ObjectID, ObjectIdColumn} from 'typeorm'

@Entity()
export class User {

  @ObjectIdColumn()
  @Transform((id: ObjectID) => id.toHexString(), {toPlainOnly: true})
  id: ObjectID

  @Column()
  username: string
}
pleerock commented 6 years ago

Glad that you found a solution.

Kalhama commented 6 years ago

I've to come back to this issue, since I found it still was not properly resolved. I had accidentally left my controller file like this:


import {Body, Delete, Get, JsonController, NotFoundError, Param, Post, Put} from 'routing-controllers'
import { createConnection } from 'typeorm'
import { User } from './entity/User'

@JsonController()
export class Routes {

  @Get('/')
  public async index() {
    try {
      const conn = await createConnection()

      const user = new User()
      user.username = 'test'
      await conn.mongoManager.save(user)

      return JSON.parse(JSON.stringify(await conn.mongoManager.find(User)))
    } catch (e) {
      console.log(e)
    }
  }
}

Which indeed resolves this issue, but is not clean solution of any kind. When I tried removing that part and going with the Transform, I ended up with the following issue

TypeError: id.toHexString is not a function
    at TransformMetadata.class_transformer_1.Transform [as transformFn] (/Users/max/Projects/backend-boilerplate/src/entity/User.js:18:46)
    at /Users/max/Projects/backend-boilerplate/node_modules/class-transformer/TransformOperationExecutor.js:211:30
    at Array.forEach (<anonymous>)
    at TransformOperationExecutor.applyCustomTransformations (/Users/max/Projects/backend-boilerplate/node_modules/class-transformer/TransformOperationExecutor.js:210:19)
    at _loop_1 (/Users/max/Projects/backend-boilerplate/node_modules/class-transformer/TransformOperationExecutor.js:155:41)
    at TransformOperationExecutor.transform (/Users/max/Projects/backend-boilerplate/node_modules/class-transformer/TransformOperationExecutor.js:178:17)
    at /Users/max/Projects/backend-boilerplate/node_modules/class-transformer/TransformOperationExecutor.js:33:41
    at Array.forEach (<anonymous>)
    at TransformOperationExecutor.transform (/Users/max/Projects/backend-boilerplate/node_modules/class-transformer/TransformOperationExecutor.js:30:19)
    at ClassTransformer.classToPlain (/Users/max/Projects/backend-boilerplate/node_modules/class-transformer/ClassTransformer.js:9:25)

How it appears for me, is that for some reason TypeORM fails to cast id as a MongoDB object ObjectID, which would include the toHexString function. Also doing typeof id in Transform function verifies this, as it's returning native JS object rather than ObjectID.

See the full repo having this issue: https://bitbucket.org/vertics/typescript-typeorm-mongo-boilerplate/src

pleerock commented 6 years ago

typeof never returns you an instance name. Can you try toString function?

Kalhama commented 6 years ago

It gives following output


[
    {
        "id": "[object Object]",
        "username": "test"
    },
   ...
]
pleerock commented 6 years ago
        const firstPost = new Post();
        firstPost.title = "Post #1";
        firstPost.text = "Everything about post #1";
        await postRepository.save(firstPost);
        console.log("id", firstPost.id);
        console.log("toHexString", firstPost.id.toHexString());
        console.log("typeof", typeof firstPost.id.toHexString());

emits:

id 5ae026714848ba1414795794
toHexString 5ae026714848ba1414795794
typeof string
Kalhama commented 6 years ago

Hi, I was able to finally resolve this issue by skipping routing-controllers and using Express instead. I think I'm going to make a separate ticket for this in routing-controllers or in class-transform, since I still can't access toHexString in class-transformer/Transform and OR which causes routing-controllers not to handle the class to JSON serialisation properly.

Thank you for your help!

jednano commented 6 years ago

@Kalhama where did you land with this? I'm experiencing the exact same issues.

Kalhama commented 6 years ago

@jedmao Hi!

If I can recall correctly I was able to do dirty fix for this issue by re-implementing toHexString from mongoose in @Transform from class-transformer. I presented the dirty fix in TypeORM's repository where I had ticket for this issue.

OT: We decided to later migrate away from using Mongo with Typeorm since many of the features we had to have in our database were not implemented in Mongo at all or then are just not that mature yet they would be working properly with TypeORM. (Such as transactions, rich JOIN queries or strict data models / schemas).

Nowadays we're happily using PostgreSQL and TypeORM in multiple production applications and haven't encountered any problems. I'd recommend you to go with another database than Mongo unless you're going to contribute on Mongo's TypeORM development or just playing around.