Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON.stringify() doesn't know how to serialize a BigInt #30

Closed
cliffhall opened this issue Aug 14, 2019 · 19 comments
Closed

JSON.stringify() doesn't know how to serialize a BigInt #30

cliffhall opened this issue Aug 14, 2019 · 19 comments

Comments

@cliffhall
Copy link

Not certain how to handle this situation:

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

      39 |     toObject() {
    > 40 |         return JSON.parse(JSON.stringify(this));
         |                                ^
      41 |     }
@jakobkummerow
Copy link
Collaborator

Working as intended/spec'ed: https://tc39.es/proposal-bigint/#sec-serializejsonproperty (step 10).

In short, the background is that JSON is so commonly used among all sorts of different systems and programming languages that it is effectively impossible to add anything to the format without breaking compatibility in some crucial case.

You can define your own .toJSON() function, and pass a corresponding "reviver" function as an argument to JSON.parse(). That way, at least you have control over any (backwards) compatibility issues.

@cliffhall
Copy link
Author

cliffhall commented Aug 14, 2019

@jakobkummerow Ok, makes sense. For anyone else stumbling upon this, the verified TL;DR fix for the above problem is:

    toObject() {
        return JSON.parse(JSON.stringify(this, (key, value) =>
            typeof value === 'bigint'
                ? value.toString()
                : value // return everything else unchanged
        ));
    }

GhbSmwc added a commit to GhbSmwc/SMW_Addressmapper that referenced this issue Jul 9, 2021
-changed var to let, as recommended by mozilla: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Variables#the_difference_between_var_and_let

-The duplicate detector had bugs that assumes two entries are different that should be the same, which is now fixed:
--Whether or not if there is a tab after the size/ending_address
--If there are a leading zero in the size/ending_address.
--I also learn that you cannot use JSON.stringify() if the thing has a bigint data type in it (results in this: GoogleChromeLabs/jsbi#30 )
@ADTC
Copy link

ADTC commented Oct 27, 2021

Wouldn't it be easier to just monkey patch (hijack) BigInt? That's the MDN recommendation.

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

If you do it this way, you don't have to add an anonymous function into every call of JSON.stringify. It applies globally to all such calls.

@nick-0101
Copy link

@ADTC answer will only work with pure javascript. With typescript I get the error: TS2339: Property 'toJSON' does not exist on type 'BigInt'.

@mohamed040406
Copy link

mohamed040406 commented Jan 5, 2022

@ ADTC answer will only work with pure javascript. With typescript I get the error: TS2339: Property 'toJSON' does not exist on type 'BigInt'.

@phoenixbeats01 you can do this if you are using typescript

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

@HolgerJeromin
Copy link

HolgerJeromin commented Jan 5, 2022

Or cleaner with the "I know what I do" feature:

BigInt.prototype["toJSON"] = function () {
  return this.toString();
};

Or better (for strict TS configs) with the TS declaration:

// eslint-disable-next-line @typescript-eslint/no-redeclare
interface BigInt {
    /** Convert to BigInt to string form in JSON.stringify */
    toJSON: () => string;
}
BigInt.prototype.toJSON = function () {
    return this.toString();
};

@brunoamancio
Copy link

Or cleaner with the "I know what I do" feature:

BigInt.prototype["toJSON"] = function () {
  return this.toString();
};

Element implicitly has an 'any' type because expression of type '"toJSON"' can't be used to index type 'BigInt'.
Property 'toJSON' does not exist on type 'BigInt'.ts(7053)

@HolgerJeromin
Copy link

Element implicitly has an 'any' type because expression of type '"toJSON"' can't be used to index type 'BigInt'.
Property 'toJSON' does not exist on type 'BigInt'.ts(7053)

Oh, I had suppressImplicitAnyIndexErrors: true in my config. Edited my example. Thanks!

@lucasvalentee
Copy link

lucasvalentee commented Mar 13, 2023

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.

@sullof
Copy link

sullof commented May 10, 2023

The issue with serializing a BigInt as a string is that during the parsing you must know what any property is to know which ones should stay as strings and which ones should become big integers.

It would be better to use an approach similar to how BigNumber instances are stringified like this

{ 
  "type": "BigNumber", 
  "hex": "0x01e848"
}

I just added to bigNumberify support for BigInt values, so that the BigInt 125000n will be encoded as

{ 
  "type": "BigInt", 
  "hex": "0x01e848"
}

and parsed back without ambiguities. Any feedback would be appreciated.

@Zetazzz
Copy link

Zetazzz commented Jun 27, 2023

I guess the process can be like this:
stringify:

function replacer(key, value) {
  if (typeof value === 'bigint') {
    return {
      type: 'bigint',
      value: value.toString()
    };
  } else {
    return value;
  }
}

const obj = {
  smallInt: 1,
  n: BigInt(9007199254740991),
};

let s = JSON.stringify(obj, replacer) //{"smallInt":1,"n":{"type":"bigint","value":"9007199254740991"}}

parse:

function reviver(key, value) {
  if (value && value.type == 'bigint') {
    return BigInt(value.value);  
  }
  return value;
}

const parsedObj = JSON.parse(s, reviver); //{ smallInt: 1, n: 9007199254740991n }

Hope this makes sense

@kodejuice
Copy link

I guess the process can be like this: stringify:

function replacer(key, value) {
  if (typeof value === 'bigint') {
    return {
      type: 'bigint',
      value: value.toString()
    };
  } else {
    return value;
  }
}

const obj = {
  smallInt: 1,
  n: BigInt(9007199254740991),
};

let s = JSON.stringify(obj, replacer) //{"smallInt":1,"n":{"type":"bigint","value":"9007199254740991"}}

parse:

function reviver(key, value) {
  if (value && value.type == 'bigint') {
    return BigInt(value.value);  
  }
  return value;
}

const parsedObj = JSON.parse(s, reviver); //{ smallInt: 1, n: 9007199254740991n }

Hope this makes sense

This is it!
Here i added a wrapper function:

// Wrapper around JSON stringify/parse methods to support bigint serialization

function replacer(key, value) {
  if (typeof value === "bigint") {
    return {
      __type: "bigint",
      __value: value.toString(),
    };
  } else {
    return value;
  }
}

function reviver(key, value) {
  if (value && value.__type == "bigint") {
    return BigInt(value.__value);
  }
  return value;
}

export const json_stringify = (obj) => {
  return JSON.stringify(obj, replacer);
};

export const json_parse = (s) => {
  return JSON.parse(s, reviver);
};

@SalamandraDevs
Copy link

SalamandraDevs commented Sep 15, 2023

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();
};

I found this solution more elegant, works for typescript too:

utils.ts

Object.defineProperty(BigInt.prototype, "toJSON", {
        get() {
            "use strict";
            return () => String(this);
        }
    });

As somebody of you have commented before, you can put this into a file and import it to have it into the global scope with import './utils.js'.

@SashaDesigN
Copy link

Wouldn't it be easier to just monkey patch (hijack) BigInt? That's the MDN recommendation.

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

If you do it this way, you don't have to add an anonymous function into every call of JSON.stringify. It applies globally to all such calls.

worked for me elegant

@orhan-cmd
Copy link

JSON.parse(JSON.stringify(String(this))) worked for me.

@joematune
Copy link

Modifying the global BigInt and involving @ts-ignore is a code smell. @Zetazzz provided a solution that highlights the built-in ability to solve this problem in both JavaScript and TypeScript.

Please don't modify the global so that the next developer has to spend time finding your elegant solution 🙏

function bigIntReplacer(_key: string, value: any): any {
  return typeof value === 'bigint' ? value.toString() : value;
}

JSON.stringify({ "my_big_int": 1122n }, bigIntReplacer);
// '{"my_big_int":"1122"}'

@remotemerge
Copy link

I am using strict typing, and the following setup works for me:

// src/types/global.d.ts
interface BigInt {
  toJSON: () => string;
}
// src/main.ts
BigInt.prototype.toJSON = function () {
  return this.toString();
};

@JohanAlteruna
Copy link

JohanAlteruna commented Jun 12, 2024

The JSON specification allows arbitrarily large integers, and in some cases you might want to actually serialize bigints as syntactical JSON integers and not strings; e.g. when communicating with services that properly support parsing such integers. You can now do this using JSON.rawJSON (if your runtime supports it).

For example:

function bigintReplacer(key, value) {
  if (typeof value === "bigint") {
    return JSON.rawJSON(value.toString());
  } else {
    return value;
  }
}

// Returns '{"foo":119257819285912598125125}'
JSON.stringify({ foo: 119257819285912598125125n }, bigintReplacer)

Note however that this only works for serialization and not for parsing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests