express icon indicating copy to clipboard operation
express copied to clipboard

Allow specifying JSON stringifier

Open brandon-leapyear opened this issue 3 years ago • 18 comments

It would be great to be able to override the JSON.stringify call in res.json(), to use json-bigint to serialize JSON objects

brandon-leapyear avatar Nov 02 '20 23:11 brandon-leapyear

It looks like you opened a PR on this feature request before we could understand more. In order to ensure a conversation does not get fragmented, we will close this issue or the PR. Which one would you prefer us to keep open?

dougwilson avatar Nov 02 '20 23:11 dougwilson

:sparkles: This is an old work account. Please reference @brandonchinn178 for all future communication :sparkles:


You can close the PR until discussion here has concluded. Alternatively, I can change the PR to draft mode?

brandon-leapyear avatar Nov 02 '20 23:11 brandon-leapyear

As long as draft mode prevents others from accidentally commenting on the pr that is fine 👍

I saw your issue open when I was running errands (and still am), but was going to ask what this helps enable that the json replacer setting cannot do. I looked at the module you referenced but couldn't see it documented what the stringify actually did (but I am mobile, so could have missed it).

I know javascript has BigInt and JSON.stringify works with those no problems. Can you show what object you are trying to stringify that gives the wrong output?

dougwilson avatar Nov 02 '20 23:11 dougwilson

:sparkles: This is an old work account. Please reference @brandonchinn178 for all future communication :sparkles:


Technically speaking, people can still comment on closed PRs. I turned the PR into draft mode and added a comment to discuss on the issue page until further notice.

I know javascript has BigInt and JSON.stringify works with those no problems.

This isn't true. Neither JSON.parse nor JSON.stringify have BigInt support, which is a major source of frustration for me right now :)

> 123948124923742397432n
123948124923742397432n

> JSON.parse('{"x":123948124923742397432}')
{ x: 123948124923742400000 }

> JSON.stringify(123948124923742397432n)
Uncaught TypeError: Do not know how to serialize a BigInt
    at JSON.stringify (<anonymous>)

For now, we're using the json-bigint library in order to have BigInt support when serializing/deserializing JSON. That library has two options: either use the native BigInt or use the bignumber.js library. For other reasons that I won't go into, we're using the bignumber.js implementation, whose default JSON representation is a string

> const BigNumber = require('bignumber.js')
> let x = new BigNumber('123948124923742397432')
> JSON.stringify({ x })
'{"x":"123948124923742397432"}'

In order to get it to serialize as a number, you have to use json-bigint's function

> const JSONbig = require('json-bigint')
> JSONbig.stringify({ x })
'{"x":123948124923742397432}'

brandon-leapyear avatar Nov 02 '20 23:11 brandon-leapyear

Ah, gotcha. So it would seem like using the json replacer option would work great for you use case. Is there something wrong with that setting that is keeping it from working for you?

dougwilson avatar Nov 02 '20 23:11 dougwilson

:sparkles: This is an old work account. Please reference @brandonchinn178 for all future communication :sparkles:


I don't think that would work. The problem is if the replacer function converted a BigInt/BigNumber into a number, it would lose the precision and get rounded off. But if the replacer function returned the BigInt/BigNumber as a string (to keep the precision), it would serialize it as a string, not as a JSON number.

I don't think there's a way to use the replacer function to return a string and tell JSON.stringify "hey trust me this is a JSON number, interpolate it directly without quotes".

brandon-leapyear avatar Nov 03 '20 00:11 brandon-leapyear

:sparkles: This is an old work account. Please reference @brandonchinn178 for all future communication :sparkles:


@dougwilson Any update on this?

brandon-leapyear avatar Nov 13 '20 21:11 brandon-leapyear

@dougwilson I think @brandon-leapyear makes a good case for why the json replacer function isn't adequate. I too think merging his PR would add powerful capabilities to Express. Any further thoughts?

davidgoli avatar Dec 08 '20 00:12 davidgoli

yeah @dougwilson i also think that @brandon-leapyear PR will help express

enyoghasim avatar Mar 15 '21 13:03 enyoghasim

@dougwilson I'm having to work around this issue as well, so agree that merging this PR would improve Express overall. Thanks @brandon-leapyear for taking the time with the fix.

EdOlson-Morgan avatar Apr 23 '21 03:04 EdOlson-Morgan

Hey people. Any updates on this? @brandon-leapyear 's PR hasn't been merged yet

andyfaizan avatar Sep 22 '21 11:09 andyfaizan

Hi -- wondering if there are any updates on this, as I'm struggling with the same at the moment.

@brandon-leapyear or others on the thread, would you mind sharing any current workarounds you have for this problem?

juanfabrega avatar Apr 23 '22 11:04 juanfabrega

:sparkles: This is an old work account. Please reference @brandonchinn178 for all future communication :sparkles:


I believe the current workaround is to not use res.json, but manually run JSONbig.stringify. We have a custom middleware in NestJS that does this automatically.

On Sat, Apr 23, 2022, 4:29 AM juanfabrega @.***> wrote:

Hi -- wondering if there are any updates on this, as I'm struggling with the same at the moment.

@brandon-leapyear https://github.com/brandon-leapyear or others on the thread, would you mind sharing any current workarounds you have for this problem?

— Reply to this email directly, view it on GitHub https://github.com/expressjs/express/issues/4453#issuecomment-1107456201, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGUC75MGZEE6E4KGOIG3S5TVGPNKHANCNFSM4TIBOZRA . You are receiving this because you were mentioned.Message ID: @.***>

brandon-leapyear avatar Apr 23 '22 16:04 brandon-leapyear

For anybody else looking at this issue, my temporary solution was to use the json-bigint library and just override the JSON methods for my whole app. This may be too drastic for some but with the codebase I have, it does the job and I have no qualms about it since it is transparent otherwise.

I just put this code at the top of my app.ts

import JSONBig from 'json-bigint';


// DANGEROUSLY override JSON prototype methods to handle big ints.
JSON.parse = JSONBig.parse;
JSON.stringify = JSONBig.stringify;

juanfabrega avatar Apr 24 '22 00:04 juanfabrega

I believe the current workaround is to not use res.json, but manually run JSONbig.stringify. We have a custom middleware in NestJS that does this automatically. On Sat, Apr 23, 2022, 4:29 AM juanfabrega @.> wrote: Hi -- wondering if there are any updates on this, as I'm struggling with the same at the moment. @brandon-leapyear https://github.com/brandon-leapyear or others on the thread, would you mind sharing any current workarounds you have for this problem? — Reply to this email directly, view it on GitHub <#4453 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGUC75MGZEE6E4KGOIG3S5TVGPNKHANCNFSM4TIBOZRA . You are receiving this because you were mentioned.Message ID: @.>

Hey, I'm also figuring out a way to stringify BigInt using middleware. From the docs I understand one has to implement a middleware this way:

import { Request, Response, NextFunction } from 'express';

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};

So I guess in your custom middleware, you must have modified the res object and manually stringified the BigInt values? Or is it a little more complex to implement, if you can share a code snippet, it would be super helpful!

rohanrajpal avatar Jul 23 '22 16:07 rohanrajpal

@rohanrajpal I wrote a simple Nest JS interceptor which utilizes the json-bigint package under the hood which you may mind useful:

import {
	Injectable,
	NestInterceptor,
	ExecutionContext,
	CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import JSONBig from "json-bigint";

@Injectable()
export class BigintInterceptor implements NestInterceptor {
	intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
		return next.handle().pipe(
			map((data) => {
				return JSONBig.parse(JSONBig.stringify(data));
			}),
		);
	}
}

danielwarke avatar May 18 '23 16:05 danielwarke

In the mozilla docs they are recommending to create your own toJSON prototype function. So you dont need an extra package for it, of course you have to handle that on the client side too. BigInt.prototype.toJSON = function () { return this.toString(); };

Toaster2-0 avatar Jul 03 '23 16:07 Toaster2-0

Even so, it would still be valuable to have this feature. For example, such feature would not force changing the API contracts on existing APIs, if you want to convert to strings it will break previous clients. client1 sending {"some":123} ,

client2 sending {"some":123291389123893981238921893892198321893198}. Can still work well if serialization and deserialization is done with json-bigint.

emilcondrea avatar Feb 22 '24 12:02 emilcondrea