Gathering detailed insights and metrics for lossless-json
Gathering detailed insights and metrics for lossless-json
Gathering detailed insights and metrics for lossless-json
Gathering detailed insights and metrics for lossless-json
@types/lossless-json
TypeScript definitions for lossless-json
json-asty
Lossless JSON-to-AST Parser and AST-to-JSON Generator
lossless-json-no-nullish-coalescing-operator
Temporary fork of lossless-json
@xtao-org/jsonhilo
Pure JavaScript minimal lossless JSON parse event streaming, akin to SAX. Fast, modular, and dependency-free.
Parse JSON without risk of losing numeric information
npm install lossless-json
99.5
Supply Chain
99.6
Quality
78.5
Maintenance
100
Vulnerability
100
License
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
429 Stars
444 Commits
28 Forks
5 Watching
1 Branches
3 Contributors
Updated on 26 Nov 2024
Minified
Minified + Gzipped
TypeScript (92.94%)
JavaScript (5.49%)
HTML (1.57%)
Cumulative downloads
Total Downloads
Last day
-17.5%
28,586
Compared to previous day
Last week
14.6%
185,420
Compared to previous week
Last month
20.4%
691,149
Compared to previous month
Last year
52.2%
5,108,078
Compared to previous year
22
Parse JSON without risk of losing numeric information.
1import { parse, stringify } from 'lossless-json' 2 3const text = '{"decimal":2.370,"long":9123372036854000123,"big":2.3e+500}' 4 5// JSON.parse will lose some digits and a whole number: 6console.log(JSON.stringify(JSON.parse(text))) 7// '{"decimal":2.37,"long":9123372036854000000,"big":null}' 8// WHOOPS!!! 9 10// LosslessJSON.parse will preserve all numbers and even the formatting: 11console.log(stringify(parse(text))) 12// '{"decimal":2.370,"long":9123372036854000123,"big":2.3e+500}'
The following in-depth article explains what happens there: Why does JSON.parse corrupt large numbers and how to solve this?
How does it work? The library works exactly the same as the native JSON.parse
and JSON.stringify
. The difference is that lossless-json
preserves information of big numbers. lossless-json
parses numeric values not as a regular number but as a LosslessNumber
, a lightweight class which stores the numeric value as a string. One can perform regular operations with a LosslessNumber
, and it will throw an error when this would result in losing information.
When to use? If you have to deal with JSON data that contains long
values for example, coming from an application like C++, Java, or C#. The trade-off is that lossless-json
is slower than the native JSON.parse
and JSON.stringify
functions, so be careful when performance is a bottleneck for you.
Features:
bigint
.Date
(turned off by default).BigNumber
, bigint
, number
, or a mix of them.JSON.parse
and JSON.stringify
.Install via npm:
npm install lossless-json
Parsing and stringification works as you're used to:
1import { parse, stringify } from 'lossless-json' 2 3const json = parse('{"foo":"bar"}') // {foo: 'bar'} 4const text = stringify(json) // '{"foo":"bar"}'
Numbers are parsed into a LosslessNumber
, which can be used like a regular number in numeric operations. Converting to a number will throw an error when this would result in losing information due to truncation, overflow, or underflow.
1import { parse } from 'lossless-json' 2 3const text = '{"normal":2.3,"long":123456789012345678901,"big":2.3e+500}' 4const json = parse(text) 5 6console.log(json.normal.isLosslessNumber) // true 7console.log(json.normal.valueOf()) // number, 2.3 8 9// LosslessNumbers can be used as regular numbers 10console.log(json.normal + 2) // number, 4.3 11 12// but the following operation will throw an error as it would result in information loss 13console.log(json.long + 1) 14// throws Error: Cannot safely convert LosslessNumber to number: 15// "123456789012345678901" will be parsed as 123456789012345680000 and lose information
JavaScript natively supports bigint
: big integers that can hold a large number of digits, instead of the about 15 digits that a regular number
can hold. It is a typical use case to want to parse integer numbers into a bigint
, and all other values into a regular number
. This can be achieved with a custom numberParser
:
1import { parse, isInteger } from 'lossless-json' 2 3// parse integer values into a bigint, and use a regular number otherwise 4export function customNumberParser(value) { 5 return isInteger(value) ? BigInt(value) : parseFloat(value) 6} 7 8const text = '[123456789123456789123456789, 2.3, 123]' 9const json = parse(text, null, customNumberParser) 10// output: 11// [ 12// 123456789123456789123456789n, // bigint 13// 2.3, // number 14// 123n // bigint 15// ]
You can adjust the logic to your liking, using utility functions like isInteger
, isNumber
, isSafeNumber
. The number parser shown above is included in the library and is named parseNumberAndBigInt
.
If you want parse a json string into an object with regular numbers, but want to validate that no numeric information is lost, you write your own number parser and use isSafeNumber
to validate the numbers:
1import { parse, isSafeNumber } from 'lossless-json' 2 3function parseAndValidateNumber(value) { 4 if (!isSafeNumber(value)) { 5 throw new Error(`Cannot safely convert value '${value}' into a number`) 6 } 7 8 return parseFloat(value) 9} 10 11// will parse with success if all values can be represented with a number 12let json = parse('[1,2,3]', undefined, parseAndValidateNumber) 13console.log(json) // [1, 2, 3] (regular numbers) 14 15// will throw an error when some of the values are too large to represent correctly as number 16try { 17 let json = parse('[1,2e+500,3]', undefined, parseAndValidateNumber) 18} catch (err) { 19 console.log(err) // throws Error 'Cannot safely convert value '2e+500' into a number' 20}
To use the library in conjunction with your favorite BigNumber library, for example decimal.js. You have to define a custom number parser and stringifier:
1import { parse, stringify } from 'lossless-json' 2import Decimal from 'decimal.js' 3 4const parseDecimal = (value) => new Decimal(value) 5 6const decimalStringifier = { 7 test: (value) => Decimal.isDecimal(value), 8 stringify: (value) => value.toString() 9} 10 11// parse JSON, operate on a Decimal value, then stringify again 12const text = '{"value":2.3e500}' 13const json = parse(text, undefined, parseDecimal) // {value: new Decimal('2.3e500')} 14const output = { 15 // {result: new Decimal('4.6e500')} 16 result: json.value.times(2) 17} 18const str = stringify(output, undefined, undefined, [decimalStringifier]) 19// '{"result":4.6e500}'
The library is compatible with the native JSON.parse
and JSON.stringify
, and also comes with the optional reviver
and replacer
arguments that allow you to serialize for example data classes in a custom way. Here is an example demonstrating how you can stringify a Date
in a different way than the built-in reviveDate
utility function.
The following example stringifies a Date
as an object with a $date
key instead of a string, so it is uniquely recognizable when parsing the structure:
1import { parse, stringify } from 'lossless-json' 2 3// stringify a Date as a unique object with a key '$date', so it is recognizable 4function customDateReplacer(key, value) { 5 if (value instanceof Date) { 6 return { 7 $date: value.toISOString() 8 } 9 } 10 11 return value 12} 13 14function isJSONDateObject(value) { 15 return value && typeof value === 'object' && typeof value.$date === 'string' 16} 17 18function customDateReviver(key, value) { 19 if (isJSONDateObject(value)) { 20 return new Date(value.$date) 21 } 22 23 return value 24} 25 26const record = { 27 message: 'Hello World', 28 timestamp: new Date('2022-08-30T09:00:00Z') 29} 30 31const text = stringify(record, customDateReplacer) 32console.log(text) 33// output: 34// '{"message":"Hello World","timestamp":{"$date":"2022-08-30T09:00:00.000Z"}}' 35 36const parsed = parse(text, customDateReviver) 37console.log(parsed) 38// output: 39// { 40// action: 'create', 41// timestamp: new Date('2022-08-30T09:00:00.000Z') 42// }
The LosslessJSON.parse()
function parses a string as JSON, optionally transforming the value produced by parsing.
{string} text
The string to parse as JSON. See the JSON object for a description of JSON syntax.{(key: string, value: unknown) => unknown} [reviver]
If a function, prescribes how the value originally produced by parsing is transformed, before being returned.{function(value: string) : unknown} [parseNumber]
Pass an optional custom number parser. Input is a string, and the output can be any numeric value: number
, bigint
, LosslessNumber
, or a custom BigNumber
library. By default, all numeric values are parsed into a LosslessNumber
.{unknown}
Returns the Object corresponding to the given JSON text.The LosslessJSON.stringify()
function converts a JavaScript value to a JSON string, optionally replacing values if a replacer function is specified, or optionally including only the specified properties if a replacer array is specified.
{unknown} value
The value to convert to a JSON string.{((key: string, value: unknown) => unknown) | Array.<string | number>} [replacer]
A function that alters the behavior of the stringification process, or an array with strings or numbers that serve as a whitelist for selecting the properties of the value object to be included in the JSON string. If this value is null
or not provided, all properties of the object are included in the resulting JSON string.{number | string | undefined} [space]
A string
or number
that is used to insert white space into the output JSON string for readability purposes. If this is a number
, it indicates the number of space characters to use as white space. Values less than 1 indicate that no space should be used. If this is a string
, the string
is used as white space. If this parameter is not provided (or is null
), no white space is used.{Array<{test: (value: unknown) => boolean, stringify: (value: unknown) => string}>} [numberStringifiers]
An optional list with additional number stringifiers, for example to serialize a BigNumber
. The output of the function must be valid stringified JSON number. When undefined
is returned, the property will be deleted from the object. The difference with using a replacer
is that the output of a replacer
must be JSON and will be stringified afterwards, whereas the output of the numberStringifiers
is already stringified JSON.{string | undefined}
Returns the string representation of the JSON object.numberStringifiers
does not return valid output.new LosslessNumber(value: number | string) : LosslessNumber
.valueOf(): number | bigint
Convert the LosslessNumber
into a regular number
or bigint
. A number
is returned for safe numbers and decimal values that only lose some insignificant digits. A bigint
is returned for large integer numbers. An Error
is thrown for values that will overflow or underflow. Examples:
1// a safe number 2console.log(new LosslessNumber('23.4').valueOf()) 3// number 23.4 4 5// a decimal losing insignificant digits 6console.log(new LosslessNumber('0.66666666666666666666667').valueOf()) 7// number 0.6666666666666666 8 9// a large integer 10console.log(new LosslessNumber('9123372036854000123').valueOf()) 11// bigint 9123372036854000123 12 13// a value that will overflow 14console.log(new LosslessNumber('2.3e+500').valueOf()) 15// Error: Cannot safely convert to number: the value '2.3e+500' would overflow and become Infinity 16 17// a value that will underflow 18console.log(new LosslessNumber('2.3e-500').valueOf()) 19// Error: Cannot safely convert to number: the value '2.3e-500' would underflow and become 0
Note that you can implement your own strategy for conversion by just getting the value as string via .toString()
, and using util functions like isInteger
, isSafeNumber
, getUnsafeNumberReason
, and toSafeNumberOrThrow
to convert it to a numeric value.
.toString() : string
Get the string representation of the lossless number.
{boolean} .isLosslessNumber : true
Lossless numbers contain a property isLosslessNumber
which can be used to
check whether some variable contains LosslessNumber.isInteger(value: string) : boolean
Test whether a string contains an integer value, like '2300'
or 10
.
isNumber(value: string) : boolean
Test whether a string contains a numeric value, like '2.4'
or '1.4e+3'
.
isSafeNumber(value: string, config?: { approx: boolean }): boolean
Test whether a string contains a numeric value which can be safely represented by a JavaScript number
without losing any information. Returns false when digits would be truncated of an integer or decimal, or when the number would overflow or underflow. When passing { approx: true }
as config, the function will be less strict and allow losing insignificant digits of a decimal value. Examples:
1isSafeNumber('1.55e3') // true 2isSafeNumber('2e500') // false 3isSafeNumber('2e-500') // false 4isSafeNumber('9123372036854000123') // false 5isSafeNumber('0.66666666666666666667') // false 6isSafeNumber('9123372036854000123', { approx: true }) // false 7isSafeNumber('0.66666666666666666667', { approx: true }) // true
toSafeNumberOrThrow(value: string, config?: { approx: boolean }) : number
Convert a string into a number when it is safe to do so, otherwise throw an informative error.
getUnsafeNumberReason(value): UnsafeNumberReason | undefined
When the provided value
is an unsafe number, describe what the reason is: overflow
, underflow
, truncate_integer
, truncate_float
. Returns undefined
when the value is safe.
isLosslessNumber(value: unknown) : boolean
Test whether a value is a LosslessNumber
.
toLosslessNumber(value: number) : LosslessNumber
Convert a number
into a LosslessNumber
. The function will throw an exception when the number
is exceeding the maximum safe limit of 15 digits (hence being truncated itself) or is NaN
or Infinity
.
parseLosslessNumber(value: string) : LosslessNumber
The default numberParser
used by parse
. Creates a LosslessNumber
from a string containing a numeric value.
parseNumberAndBigInt(value: string) : number | bigint
A custom numberParser
that can be used by parse
. The parser will convert integer values into bigint
, and converts al other values into a regular number
.
reviveDate(key, value)
Revive strings containing an ISO 8601 date string into a JavaScript Date
object. This reviver is not turned on by default because there is a small risk of parsing a text field that accidentally contains a date into a Date
. Whether reviveDate
is safe to use depends on the use case. Usage:
1import { parse, reviveDate } from 'lossless-json' 2 3const data = parse('["2022-08-25T09:39:19.288Z"]', reviveDate) 4// output: 5// [ 6// new Date('2022-08-25T09:39:19.288Z') 7// ]
An alternative solution is to stringify a Date
in a specific recognizable object like {'$date':'2022-08-25T09:39:19.288Z'}
, and use a reviver and replacer to turn this object into a Date
and vice versa.
Similar libraries:
To test the library, first install dependencies once:
npm install
To run the unit tests:
npm test
To build the library and run the unit tests and integration tests:
npm run build-and-test
Run linting:
npm run lint
Fix linting issues automatically:
npm run format
To run a benchmark to compare the performance with the native JSON
parser:
npm run benchmark
(Spoiler: lossless-json
is much slower than native)
To build a bundled and minified library (ES5), first install the dependencies once:
npm install
Then bundle the code:
npm run build
This will generate an ES module output and an UMD bundle in the folder ./.lib
which can be executed in browsers and node.js and used in the browser.
To release a new version:
$ npm run release
This will:
To try the build and see the change list without actually publishing:
$ npm run release-dry-run
Released under the MIT license.
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
Reason
1 existing vulnerabilities detected
Details
Reason
6 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 6
Reason
Found 0/30 approved changesets -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More