prisma / studio

🎙️ The easiest way to explore and manipulate your data in all of your Prisma projects.
https://www.prisma.io/studio
1.81k stars 45 forks source link

Prisma Studio or Data Browser: `Do not know how to serialize a BigInt` #614

Open yovanoc opened 3 years ago

yovanoc commented 3 years ago

On the latest version

image
KhaVNguyen commented 3 years ago

@yovanoc I am getting this exact error as well when trying to view my data inside Prisma Studio (some of my columns are BigInt's).

This can be reproduced by just having any model with any field that is aBigInt

KhaVNguyen commented 3 years ago

I tried to fix it using the proposed solution here in my own code: image

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt

I believe it needs to be done on Prisma's end though

yoroshikun commented 3 years ago

I am also experiencing this issue with BigInt. I hope this can be solved soon. This seems to affect prisma as a whole as well

sdnts commented 3 years ago

Hey everyone, I'm going to take a look at this very soon, I can reproduce the problem as well.

@yoroshikun I can't seem to be able to see an issue with Prisma Client or Migrate on 2.15.0, could you elaborate what you mean by This seems to affect prisma as a whole as well? Thanks!

yoroshikun commented 3 years ago

Sorry for the late reply, I have done some testing and the issue is not from Prisma not knowing how to deserialize but the framework next.js not being able to. (It affected both pages and API routes).

But yes as described above I can also reproduce the issue with Prisma studio and for now, have swapped to storing the values as strings as a fallback.

sdnts commented 3 years ago

Hello everyone! So this should be fixed in Prisma version 2.16.0 (due today). Please let me know if you're still seeing issues after it goes out. Note that there still is one issue https://github.com/prisma/studio/issues/621 that I'm going to tackle soon.

@yoroshikun Prisma Client returns real JS BigInts for Prisma BigInts, which aren't serializable, maybe that's why NextJS is also struggling.

tsdexter commented 3 years ago

@madebysid I am getting this error on an introspected wordpress database running on Microsoft SQL Server (azure) with Yoast (wordpress-seo) installed. The call I'm using is is in a simple express server:

app.get("/select", async (req, res) => {
  try {
    const table = req.query.table;
    const limit = parseInt(req.query.limit, 10);
    const data = await prisma[table].findMany({
      take: limit,
    });
    res.send(data);
  } catch (error) {
    res.status = 500;
    res.send({ error: true, message: error.message });
  }
});

On the route http://localhost:3001/select?table=wp_yoast_seo_links&limit=10 the response is:

{
  "error": true,
  "message": "Do not know how to serialize a BigInt"
}

How can I go about returning data with BigInts in it?

tsdexter commented 3 years ago

@madebysid I've fixed this for now by changing the schema like so:

model wp_yoast_seo_links {
  // id             BigInt  @id @default(autoincrement())
  id             Int     @id @default(autoincrement())
  url            String  @db.NVarChar(255)
  // post_id        BigInt
  post_id        Int
  // target_post_id BigInt
  target_post_id Int
  type           String  @db.NVarChar(8)
  language       String? @db.NVarChar(32)
  region         String? @db.NVarChar(32)

  @@index([post_id, type], name: "link_direction")
}

All of the BigInt's in my schema are for id and foreign key id fields on third party tables...whereas the first party tables such as wp_posts just use regular Int so I would imagine I shouldn't have any issues with this change - can you think of any possible issues with this? Thanks

sdnts commented 3 years ago

Hey @tsdexter, IIUC Studio is not involved in your setup at all, right? Is this is general question? (Happy to help either way, just wondering)

The underlying issue is that when you use a BigInt in a field in your schema, not only does your DB use a BigInt (if it is supported), but Prisma Client also now returns a real Javascript BigInt for that field. Weirdly enough, the native JSON.stringify does not work with BigInts. If you try running JSON.stringify(BigInt(34)) in a Node REPL, you'll see this error. It doesn't work in browsers either.

So in your case, the issue is that you're using res.send in your request handler, and express internally tries to JSON.stringify your response: res.send in express, express' stringify function

Again, this has nothing to do with Express, it's just that these two JS language features do not work together for whatever reason.

So here are a few ways to address this:

  1. You could change the BigInt to an Int like you've already done. This is far from ideal, since you might actually WANT a BigInt.
  2. Treat BigInts as strings. In your specific case, you might be able to get away with a BigInt().toString() to convert the integer into a string. That way, Express will see your BigInt as a string, and JSON.stringify will work as expected. It'll be manual work to make sure you change all BigInts to strings, but it might be feasible, depending on what your (client-side) app wants.
  3. Take care of stringifyingBigInts yourself. This is a lot more involved (and is ultimately what I do in Studio as well). Here's the gist of it: https://dev.to/benlesh/bigint-and-json-stringify-json-parse-2m8p. This is very similar to 2., but it works better if you want real BigInts client-side. If you prefix all BigInt strings with a prefix, you can identify such strings and run BigInt("prefix::33434".slice(0, 8)) client side to get back a real BigInt. Just be careful that you use a prefix that won't also naturally appear in regular strings.

Hopefully this was helpful and gave you a few ideas! if you have more questions, it'd probably be more useful to the community if you could start a discussion, and tag me there, I'm happy to help.

can you think of any possible issues with this? Thanks

It's hard for me to tell you what would and wouldn't work, since it is highly dependent on your app :D If you don't have a specific reason to use Ints or BigInts in your ID fields, I'd generally recommend using cuid() instead, since it usually works better (you don't have to worry about "running out" etc.), and it circumvents this issue as well!

ryands17 commented 3 years ago

@tsdexter 👋

The easiest workaround as of now is to add the toJSON method on the prototype so that serialisation works well.

BigInt.prototype.toJSON = function() {       
  return this.toString()
}
ryands17 commented 3 years ago

@snettah 👋

This will work fine if the number is in the limits that JavaScript supports. For very large numbers, you would need to use a string.

waptik commented 2 years ago

@tsdexter 👋

The easiest workaround as of now is to add the toJSON method on the prototype so that serialisation works well.

BigInt.prototype.toJSON = function() {       
  return this.toString()
}

Hi! I am using prisma client 3.5.0 with nextjs v12.0.4 and i am facing this same issue. Where do i implement your solution? I tried it in a *.d.ts file but it didn't have any effect

vrishtrix commented 2 years ago

@tsdexter 👋 The easiest workaround as of now is to add the toJSON method on the prototype so that serialisation works well.

BigInt.prototype.toJSON = function() {       
  return this.toString()
}

Hi! I am using prisma client 3.5.0 with nextjs v12.0.4 and i am facing this same issue. Where do i implement your solution? I tried it in a *.d.ts file but it didn't have any effect

I used this as a workaround.

const data = await prisma.data.findMany();
const updatedData = JSON.stringify(data, (_key, value) => {
    typeof value === 'bigint' ? value = value.toString() : value
})

return { props: { updatedData } }
roscadalexandru commented 2 years ago

this is my solution

BigInt.prototype.toJSON = function () {
  const int = Number.parseInt(this.toString());
  return int ?? this.toString();
};
necmettin commented 2 years ago

@tsdexter 👋

The easiest workaround as of now is to add the toJSON method on the prototype so that serialisation works well.

BigInt.prototype.toJSON = function() {       
  return this.toString()
}

Where do you add this?

maheshsuthar93 commented 2 years ago

@tsdexter 👋

The easiest workaround as of now is to add the toJSON method on the prototype so that serialisation works well.

BigInt.prototype.toJSON = function() {       
  return this.toString()
}

where to add this one could you send more code here?

AndreLucas2103 commented 2 years ago

@tsdexter 👋 A solução mais fácil a partir de agora é adicionar o toJSONmétodo no protótipo para que a serialização funcione bem.

BigInt . protótipo . toJSON  =  function ( )  {        
  return  this . toString ( ) 
}

onde adicionar este você poderia enviar mais código aqui?

Crie um arquivo patch.js contendo BigInt.prototype.toJSON = function() { return this.toString() }

logo após importe ele para sua fonte import './patch.js'; or require('patch.js')

frankps commented 2 years ago

Had the same issue. All above was not working, but this worked (added to src/main.ts):

(BigInt.prototype as any).toJSON = function () { return Number(this) };

or (if you want the BigInt as a string in your json):

(BigInt.prototype as any).toJSON = function () { return this.toString(); };

zkSoju commented 2 years ago

Is the best approach currently to store BigInts as strings? I'm not sure where to add the suggested implementation shown here: https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields#working-with-bigint

JayP127 commented 2 years ago

SkSoju, the same problem here zkSoju. I don't know how or where to handle the suggested implementation. Anybody from Prisma can shed some light? Thanks!

I rather use the official solution for handling BigInt; I only need a more thorough explanation from Prisma

zkSoju commented 2 years ago

SkSoju, the same problem here zkSoju. I don't know how or where to handle the suggested implementation. Anybody from Prisma can shed some light? Thanks!

I rather use the official solution for handling BigInt; I only need a more thorough explanation from Prisma

Agreed that would probably be ideal, currently using strings, but it's a bit annoying to keep track of. If anyone has any examples that would be very helpful.

zkSoju commented 2 years ago

SkSoju, the same problem here zkSoju. I don't know how or where to handle the suggested implementation. Anybody from Prisma can shed some light? Thanks!

I rather use the official solution for handling BigInt; I only need a more thorough explanation from Prisma

I'm using the official implementation in each of the service files and it works. Ideally the earlier solution could be used so you don't have to use the custom implementation for every query.

luanvdw commented 2 years ago

Hey @zkSoju, can you share the version of Prisma you are using in the project where you are experiencing the Do not know how to serialize a BigInt error?

zkSoju commented 2 years ago

Hey @zkSoju, can you share the version of Prisma you are using in the project where you are experiencing the Do not know how to serialize a BigInt error?

Hi I was actually able to fix it using graphql-scalars I figure the package specifies the serializing of BigInt.

taylor-lindores-reeves commented 2 years ago

Our tests are failing due to this. Not sure why, they were working until we upgraded to 4.0.0

Screenshot 2022-06-29 at 22 33 59

janpio commented 2 years ago

Are you testing Prisma Studio @leafyshark? If not, please report this at https://github.com/prisma/prisma and include more information. We would be very interesting in figuring this out of course. (Are you using raw queries in that test? Please include reply in the new issue. Thanks.)

taylor-lindores-reeves commented 2 years ago

Hi, we figured out that Jest upgrade meant that it can't serialize BigInt's to JSON, and when running with multiple workers it tries to send data between them. Jest crashes without the real message describing the test failure https://github.com/facebook/jest/issues/11617#issuecomment-1068732414

Solution is to set maxWorkers: 1in jest.config.js

gspasov commented 1 year ago

@tsdexter 👋

The easiest workaround as of now is to add the toJSON method on the prototype so that serialisation works well.

BigInt.prototype.toJSON = function() {       
  return this.toString()
}

If you are using TypeScript and/or ESLint, you'll need to add few more lines to make TypeScript and/or ESLint happy. The comment lines may vary depending on how you have setup your ESLint.

// eslint-disable-next-line @typescript-eslint/ban-ts-comment      <-- Necessary for my ESLint setup
// @ts-ignore: Unreachable code error                              <-- BigInt does not have `toJSON` method
BigInt.prototype.toJSON = function (): string {
  return this.toString();
};

You could also go with the approach to convert it to number like so:

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unreachable code error
BigInt.prototype.toJSON = function (): number {
  return Number(this);
};

But bare in mind that the precision that BigInt carries will be lost when converted to Number. The max value for bigserial in postgres for example is 9223372036854775807. So if you are using bigserial (which prisma will convert to BigInt, take this into consideration)

> 9223372036854775807n
9223372036854775807n
> Number(9223372036854775807n)
9223372036854776000

On the question of "Where to add this statement?" Add it to the root of your project. In my case that would be the index.ts where I initialize my server which uses Prisma as ORM.

Krillegeddon commented 1 year ago

Wherever I used JSON.serialize() in my project, I sprinkled this code next to my real class...

export class MyClass {
    // My method, doing stuff... like
    //var mySerializeString = JSON.serialize(myObject);
}

// Some bug with Prisma and BigInt... https://github.com/prisma/studio/issues/614
(BigInt.prototype as any).toJSON = function () {
    return Number(this)
};

And it seems to work. Reading a one and a half year old thread, and the solution is this - not good.

petradonka commented 1 year ago

I agree, having to use this workaround after this much time is not good enough.

We've been looking into this internally and have fixed a few cases that could lead to this error. We have a few more to go though, and reproduction is a challenge in some cases.

If you have more info on how to reproduce the issue in Studio (or Data Browser in Prisma Data Platform), we'd like to hear from you, to better understand where things go wrong and how you're using BigInts.

Krillegeddon commented 1 year ago

I tried console.log(result) after a read of bigints. Serialization worked, but I noticed that all values are printed as 1n, 2n, 3n etc.

However, when trying to do console.log(JSON.stringify(result)) - I get the error.

I have no idea why JSON should serialize bigints differently than normal ints? I think it ought to be the same, but just possibly there are more digits? But, I just tried that, and the number is truncated and exponent is shown...

However, problem seems to be in JSON and not Prisma. Prisma is building the object by the book it seems.

However, I can see there is a package called json-bigint. If they have managed to do a workaround that makes JSON work by just including a package, perhaps you can make that package a dependency to Prisma? Or implement the same thing? I haven't tried it though.

The workaround is however not very good. It just makes code not break, but if there are really big numbers (that require BigInt), it will not work. So I need to come up with a better solution before my project hits production.

Rainer2465 commented 1 year ago

@Krillegeddon is right, the problem is with JSON itself and not prisma, JSON does not support BigInt. JSON.stringify({a:10n}) will throw the same exception: 'Uncaught TypeError: Do not know how to serialize a BigInt'

btw: the 'n' suffix means it's a bigInt

I've found this article: https://dev.to/benlesh/bigint-and-json-stringify-json-parse-2m8p it helped me to make a work around

// Serialize
const json = JSON.stringify(data, (key, value) =>
  typeof value === "bigint" ? `BIGINT::${value}` : value
);

// Deserialize
const backAgain = JSON.parse(json, (key, value) => {
  if (typeof value === "string" && value.startsWith('BIGINT::')) {
    return BigInt(value.substr(8));
  }
  return value;
});

UPDATE: I've looked at the prisma and for a better work around they have a customized implementation of JSON.stringify

// now my unit service returns a units = prisma.unit.findMany(...) return JSON.stringify(units, (key, value) => (typeof value === 'bigint' ? value.toString() : value))

// and my controller returns a express reponse

const unitEntities = await unitService.getAllUnits()
response.status(200).send(unitEntities) // !IMPORTANT do not use response.json but response.send, otherwise it wont work
d-ivashchuk commented 1 year ago

@Rainer2465 @Krillegeddon @gspasov you have some nice ideas on fixing this locally for you, that's great! We would really appreciate if you give us some hints on where the error happens for you(studio/data browser) as well as any other useful information that would help us reproduce this bug locally(prisma version, schema of the model where the error happens, any other steps you found that get you to this error) Thanks a lot for helping us out!

Rainer2465 commented 1 year ago

@Rainer2465 @Krillegeddon @gspasov you have some nice ideas on fixing this locally for you, that's great! We would really appreciate if you give us some hints on where the error happens for you(studio/data browser) as well as any other useful information that would help us reproduce this bug locally(prisma version, schema of the model where the error happens, any other steps you found that get you to this error) Thanks a lot for helping us out!

Anywhere on the browser when you try to serialize a BigInt on JSON, for me for example all my models that have BigInt when I try so send it to the frontend using Express it throws this error. You can reproduce this error on your browsers console with JSON.stringify({a:10n})

image

petradonka commented 1 year ago

@Rainer2465, are you running into issues with Studio/Data Browser when using/serializing BigInts, or is this a more generic Prisma client issue for you?

alexanderattar commented 1 year ago

Any updates on this? Why is the issue closed? It still appears to be an issue

petradonka commented 1 year ago

We haven't been able to reproduce the issue and get to the bottom of it. @alexanderattar, are you consistently running into this? Is it in Studio or the Data Browser? Could you share a bit more details, such as how you're using BigInts and which steps you're taking that result in the error?

kcmv commented 1 year ago

Hi @petradonka, I am also experiencing this issue using the Browser but in Prisma Studio there's no issue.

petradonka commented 1 year ago

Thanks for reaching out @kcmv. Are you consistently running into this? Could you share a bit more details, such as how you're using BigInts and which steps you're taking that result in the error? Which Prisma Studio version are you using?

mirelalazar commented 1 year ago

Jumping in here, just got this error by simply running the following raw query: let sql = SELECT count(*) as counter FROM Users const data: any[] = await prisma.$queryRaw(Prisma.raw(sql))

petradonka commented 1 year ago

@mirelalazar, it sounds like you ran into this issue outside of Studio, just simply through the Prisma client. Is that right? If so, you could open a discussion or an issue on https://github.com/prisma/prisma to try to resolve the problem.

mcgrealife commented 1 year ago

For anyone encountering this issue in next.js, I found this article helpful https://flaviocopes.com/nextjs-serialize-date-json/ (unrelated to Prisma)

Edit: However, parsing and stringifying the prisma response loses its type inference. (So the parsed response must be type casted as a prisma type - which is inflexible to type changes).

Related unresolved issue in Nextjs https://github.com/vercel/next.js/discussions/11498

kcmv commented 1 year ago

Thanks for reaching out @kcmv. Are you consistently running into this? Could you share a bit more details, such as how you're using BigInts and which steps you're taking that result in the error? Which Prisma Studio version are you using?

Hi @petradonka, I experienced this issue whenever i try to display the bigint data into a json. My solution was to use JSON.stringify

petradonka commented 1 year ago

Thanks for the details @kcmv! We've also made some progress on this issue and will be releasing some improvements with the next Prisma release that should fix the issue in most cases.

jboothe commented 1 year ago

If you have made it this far in the thread and have found none of the suggestions above worked, you may want to try what finally worked for me. In my case, there was a cacheing issue in my app that prevented new changes from being compiled.

Simply enough all it took was to delete the /dist folder and restart npm run start:dev. Both solutions below then worked for me. Choose one or the other, or you may want to implement @Rainer2465 serialize/deserialize option if your case requires it.

Global solution

In my NestJs app, this solution works in my main.ts file:

// main.ts
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment      <-- Necessary for my ESLint setup
  // @ts-ignore: Unreachable code error                              <-- BigInt does not have `toJSON` method
  BigInt.prototype.toJSON = function (): string {
    return this.toString();
  };

Service level solution

And the suggested Prisma solution works in each service method:

// some.service.ts
async findAll() {
    const data = await this.prisma.caseType.findMany();
    return JSON.stringify(data, (key, value) =>
      typeof value === 'bigint' ? value.toString() : value,
    );
}
EnkhAmar commented 1 year ago

If you have made it this far in the thread and have found none of the suggestions above worked, you may want to try what finally worked for me. In my case, there was a cacheing issue in my app that prevented new changes from being compiled.

Simply enough all it took was to delete the /dist folder and restart npm run start:dev. Both solutions below then worked for me. Choose one or the other, or you may want to implement @Rainer2465 serialize/deserialize option if your case requires it.

Global solution

In my NestJs app, this solution works in my main.ts file:

// main.ts
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment      <-- Necessary for my ESLint setup
  // @ts-ignore: Unreachable code error                              <-- BigInt does not have `toJSON` method
  BigInt.prototype.toJSON = function (): string {
    return this.toString();
  };

Service level solution

And the suggested Prisma solution works in each service method:

// some.service.ts
async findAll() {
    const data = await this.prisma.caseType.findMany();
    return JSON.stringify(data, (key, value) =>
      typeof value === 'bigint' ? value.toString() : value,
    );
}

@jboothe Thank you. This works for me like a charm.

trevorr commented 1 year ago

No need to fight with eslint in Typescript:

declare global {
  interface BigInt {
    toJSON(): string;
  }
}

BigInt.prototype.toJSON = function (): string {
  return this.toString();
};
lucasvalentee commented 1 year ago

If you're trying to use this with Typescript, I suggest you create a .ts or .js file with:

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unreachable code error
BigInt.prototype.toJSON = function (): number {
  return this.toString();
};

You can also return Number(this), but I suggest toString as it has no character limiter. However, it will depend on your context.

After that, you must import or require this file in your main file (like index.ts/.js or main.ts, etc).

If you are trying to run tests with Jest, you must add this file in the installation configuration. These settings can be created in package.json with "jest: { .... }" or in a file called jest.config.ts (create this in the root).

Example:

export default {
  testTimeout: 30000,
  moduleFileExtensions: ['js', 'json', 'ts'],
  **setupFiles: ['./my_config_file'],**
  testRegex: '.*\\.spec\\.ts$',
  transform: {
    '^.+\\.(t|j)s$': 'ts-jest',
  },
  collectCoverageFrom: ['**/*.(t|j)s'],
  coverageDirectory: '../coverage',
  testEnvironment: 'node',
}

At this point you can create a test module, with many configs, and create an index file that imports all the configs, and just pass this index file in the setupFiles config from jest.

lucasvalentee commented 1 year ago

Our tests are failing due to this. Not sure why, they were working until we upgraded to 4.0.0

Screenshot 2022-06-29 at 22 33 59

This should help you: https://github.com/prisma/studio/issues/614#issuecomment-1466901384

phoenixeliot commented 1 year ago

I believe v4 started returning a lot more non-Number types (BigInt, Decimal, maybe others). Honestly a huge pain, we're looking for a good workaround too. The worst part is that if you get a Decimal back, + does string concatenation instead of actual addition, so it broke a lot of our backend endpoints and made them produce wrong results. We have to cast every $queryRaw count/sum/etc result with Number() now.