无损 json
解析 JSON,不会有丢失数字信息的风险。
从 'lossless-json' 导入 { parse, stringify }
const text = '{"十进制":2.370,"长":9123372036854000123,"大":2.3e+500}'
// JSON.parse 将丢失一些数字和一个整数:
console.log(JSON.stringify(JSON.parse(text)))
// '{"小数":2.37,"长":9123372036854000000,"大":空}'
// 哎呀!!!
// LosslessJSON.parse 将保留所有数字,甚至格式:
console.log(stringify(解析(文本)))
// '{"小数":2.370,"长":9123372036854000123,"大":2.3e+500}'
以下深入文章解释了那里发生的情况:[为什么 JSON.parse 会损坏大量数字以及如何解决此问题?](https://jsoneditoronline.org/indepth/parse/why-does-json-parse-corrupt- 大数/)
它是如何工作的? 该库的工作方式与本机“JSON.parse”和“JSON.stringify”完全相同。 不同之处在于“lossless-json”保留了大数字的信息。 lossless-json
不会将数值解析为常规数字,而是解析为 LosslessNumber
,这是一个将数值存储为字符串的轻量级类。 人们可以使用“LosslessNumber”执行常规操作,当这会导致信息丢失时,它会抛出错误。
何时使用? 如果您必须处理包含“长”值的 JSON 数据,例如来自 C++、Java 或 C# 等应用程序的 JSON 数据。 代价是“lossless-json”比原生的“JSON.parse”和“JSON.stringify”函数慢,因此当性能成为您的瓶颈时要小心。
特征:
- 处理大数字时没有丢失数字信息的风险。
- 保持数字的格式。
- 重复键上的解析错误。
- 对“bigint”的内置支持。
- 对“日期”的内置支持(默认关闭)。
- 可定制:将数值解析为任何数据类型,例如“BigNumber”、“bigint”、“number”或它们的混合。
- 与本机内置的“JSON.parse”和“JSON.stringify”兼容。
- 解析无效 JSON 时提供有用的错误消息。
- 适用于浏览器和 Node.js。
- 附带 TypeScript 打字功能。
- 模块化:ES模块功能,仅加载和捆绑您使用的内容。
- 压缩和压缩后,完整包的大小小于 4kB。
## 安装
通过 npm 安装:
npm 安装lossless-json
## 使用
解析和字符串化
解析和字符串化按照您习惯的方式工作:
从 'lossless-json' 导入 { parse, stringify }
const json = parse('{"foo":"bar"}') // {foo: 'bar'}
const text = stringify(json) // '{"foo":"bar"}'
无损数字
数字被解析为“LosslessNumber”,可以像数字运算中的常规数字一样使用。 如果转换为数字会因截断、上溢或下溢而导致信息丢失,则会引发错误。
从 'lossless-json' 导入 { parse }
const text = '{"正常":2.3,"长":123456789012345678901,"大":2.3e+500}'
const json = 解析(文本)
console.log(json.normal.isLosslessNumber) // true
console.log(json.normal.valueOf()) // 数字, 2.3
// LosslessNumbers 可以用作常规数字
console.log(json.normal + 2) // 数字, 4.3
// 但是下面的操作会抛出错误,因为它会导致信息丢失
console.log(json.long + 1)
// 抛出错误:无法安全地将 LosslessNumber 转换为数字:
// "123456789012345678901" 将被解析为 123456789012345680000 并丢失信息
大整数
JavaScript 原生支持“bigint”:可以容纳大量数字的大整数,而不是常规“number”可以容纳的大约 15 位数字。 一个典型的用例是将整数解析为“bigint”,并将所有其他值解析为常规“number”。 这可以通过自定义“numberParser”来实现:
从 'lossless-json' 导入 { parse, isInteger }
// 将整数值解析为 bigint,否则使用常规数字
导出函数customNumberParser(值){
返回 isInteger(值) ? BigInt(值) : parseFloat(值)
}
常量文本 = '[123456789123456789123456789, 2.3, 123]'
const json = 解析(文本,null,customNumberParser)
// 输出:
// [
// 123456789123456789123456789n, // bigint
// 2.3, // 数字
// 123n // bigint
//]
您可以使用“isInteger”、“isNumber”、“isSafeNumber”等实用函数根据自己的喜好调整逻辑。 上面显示的数字解析器包含在库中,名为“parseNumberAndBigInt”。
验证安全号码
如果您想将 json 字符串解析为具有常规数字的对象,但想验证没有丢失数字信息,您可以编写自己的数字解析器并使用“isSafeNumber”来验证数字:
从 'lossless-json' 导入 { parse, isSafeNumber }
函数 parseAndValidateNumber(值) {
if (!isSafeNumber(值)) {
抛出新错误(`无法安全地将值'$ {value}'转换为数字`)
}
返回parseFloat(值)
}
// 如果全部都解析成功值可以用数字表示
让 json = parse('[1,2,3]', 未定义, parseAndValidateNumber)
console.log(json) // [1, 2, 3](常规数字)
// 当某些值太大而无法正确表示为数字时会抛出错误
尝试 {
让 json = parse('[1,2e+500,3]', 未定义, parseAndValidateNumber)
} 捕获(错误){
console.log(err) // 抛出错误 '无法安全地将值 '2e+500' 转换为数字'
}
大数字
将该库与您最喜欢的 BigNumber 库结合使用,例如 decimal.js。 您必须定义自定义数字解析器和字符串生成器:
从 'lossless-json' 导入 { parse, stringify }
从“decimal.js”导入 Decimal
const parseDecimal = (值) => new Decimal(值)
常量十进制字符串符 = {
测试:(值)=> Decimal.isDecimal(值),
stringify: (值) => value.toString()
}
// 解析 JSON,对 Decimal 值进行操作,然后再次字符串化
const 文本 = '{"值":2.3e500}'
const json = parse(text, undefined, parseDecimal) // {value: new Decimal('2.3e500')}
常量输出 = {
// {结果: new Decimal('4.6e500')}
结果:json.value.times(2)
}
const str = stringify(输出, 未定义, 未定义, [decimalStringifier])
// '{"结果":4.6e500}'
复兴者和替代者
该库与本机“JSON.parse”和“JSON.stringify”兼容,并且还附带可选的“reviver”和“replacer”参数,允许您以自定义方式序列化例如数据类。 下面的示例演示了如何以与内置“reviveDate”实用程序函数不同的方式对“Date”进行字符串化。
以下示例将“Date”字符串化为带有“$date”键而不是字符串的对象,因此在解析结构时它是唯一可识别的:
从 'lossless-json' 导入 { parse, stringify }
// 将日期字符串化为具有键“$date”的唯一对象,因此它是可识别的
函数customDateReplacer(键,值){
if (值实例日期) {
返回 {
$date: value.toISOString()
}
}
返回值
}
函数 isJSONDateObject(值) {
返回值 && typeof value === 'object' && typeof value.$date === 'string'
}
函数customDateReviver(键,值){
if (isJSONDateObject(值)) {
返回新日期(值。$日期)
}
返回值
}
常量记录 = {
消息:“你好世界”,
时间戳:新日期('2022-08-30T09:00:00Z')
}
const text = stringify(记录,customDateReplacer)
控制台.log(文本)
// 输出:
// '{"message":"Hello World","timestamp":{"$date":"2022-08-30T09:00:00.000Z"}}'
const 已解析 = 解析(文本,customDateReviver)
console.log(已解析)
// 输出:
// {
// 动作: '创建',
// 时间戳: new Date('2022-08-30T09:00:00.000Z')
// }
API
parse(text [, reviver [, parseNumber]])
LosslessJSON.parse()
函数将字符串解析为 JSON,可以选择转换解析产生的值。
- @param
{string} 文本
要解析为 JSON 的字符串。 有关 JSON 语法的说明,请参阅 JSON 对象。 - @param
{(键:字符串,值:JSONValue)=> 未知} [reviver]
如果是函数,则规定在返回之前如何转换最初由解析生成的值。 - @param
{函数(值:字符串):未知} [parseNumber]
传递可选的自定义数字解析器。 输入是一个字符串,输出可以是任何数值:“number”、“bigint”、“LosslessNumber”或自定义“BigNumber”库。 默认情况下,所有数值都被解析为“LosslessNumber”。 - @returns
{未知}
返回与给定 JSON 文本对应的对象。 - @throws 如果要解析的字符串不是有效的 JSON,则抛出 SyntaxError 异常。
stringify(值 [, 替换符 [, 空格 [, numberStringifiers]]])
LosslessJSON.stringify()
函数将 JavaScript 值转换为 JSON 字符串,如果指定了替换函数,则可选择替换值;如果指定了替换数组,则可选择仅包含指定的属性。
- @param
{未知}值
要转换为 JSON 字符串的值。 - @param
{((键:字符串,值:未知) => 未知) | 数组。<字符串| 数字>} [替换]
更改字符串化过程行为的函数,或包含字符串或数字的数组,用作白名单,用于选择要包含在 JSON 字符串中的值对象的属性。 如果此值为“null”或未提供,则对象的所有属性都将包含在生成的 JSON 字符串中。 - @param
{数字 | 字符串| 未定义} [空格]
用于在输出 JSON 字符串中插入空格以提高可读性的“字符串”或“数字”。 如果这是一个“数字”,则表示用作空白的空格字符数。 小于 1 的值表示不应使用空间。 如果这是一个“字符串”,则“字符串”将用作空格。 如果未提供此参数(或者为“null”),则不使用空格。 - @param
{数组<{测试:(值:未知)=> 布尔值,字符串化:(值:未知)=> 字符串}>} [numberStringifiers]
带有附加数字字符串生成器的可选列表,例如用于序列化“BigNumber”。 函数的输出必须是有效的字符串化 JSON 数字。 当返回“undefined”时,该属性将从对象中删除。 与使用“replacer”的区别在于,“replacer”的输出必须是 JSON,并且随后将被字符串化,而“numberStringifiers”的输出已经是字符串化的 JSON。 - @returns
{string | 未定义}
返回 JSON 对象的字符串表示形式。 - @throws 当“numberStringifiers”之一未返回有效输出时抛出语法错误。
无损号码
#### 建造
new LosslessNumber(值: 数字 | 字符串) : LosslessNumber
#### 方法
-
.valueOf(): 数字 | 大整数
将“LosslessNumber”转换为常规“number”或“bigint”。 对于仅丢失一些无意义数字的安全数字和十进制值,将返回“数字”。 对于大整数,返回“bigint”。 对于将溢出或下溢的值,将引发“错误”。 例子:// 安全号码 console.log(new LosslessNumber('23.4').valueOf()) // 数字 23.4 // 丢失小数位的小数 console.log(new LosslessNumber('0.666666666666666666666667').valueOf()) // 数字 0.6666666666666666 // 一个大整数 console.log(new LosslessNumber('9123372036854000123').valueOf()) // bigint 9123372036854000123 // 一个会溢出的值 console.log(new LosslessNumber('2.3e+500').valueOf()) // 错误:无法安全地转换为数字:值“2.3e+500”会溢出并变为无穷大 // 一个会下溢的值 console.log(new LosslessNumber('2.3e-500').valueOf()) // 错误:无法安全地转换为数字:值“2.3e-500”将下溢并变为 0
请注意,您可以实现自己的转换策略,只需通过“.toString()”获取字符串形式的值,然后使用“isInteger”、“isSafeNumber”、“getUnsafeNumberReason”和“toSafeNumberOrThrow”等实用函数将其转换为 一个数值。
-
.toString() : 字符串
获取无损数的字符串表示形式。
#### 特性
{boolean} .isLosslessNumber : true
无损数字包含一个属性“isLosslessNumber”,可用于
检查某个变量是否包含 LosslessNumber。
实用函数
-
isInteger(值: 字符串) : 布尔值
测试字符串是否包含整数值,例如“2300”或“10”。 -
isNumber(值:字符串):布尔值
测试字符串是否包含数值,例如“2.4”或“1.4e+3”。 -
isSafeNumber(value: string, config?: { approx: boolean }): boolean
测试字符串是否包含可以用 JavaScript“数字”安全表示的数值,而不会丢失任何信息。 当数字被截断为整数或小数时,或者当数字上溢或下溢时,返回 false。 当传递{ approx: true }
作为配置时,该函数将不那么严格,并且允许丢失小数值的无意义数字。 例子:isSafeNumber('1.55e3') // true isSafeNumber('2e500') // false isSafeNumber('2e-500') // false isSafeNumber('9123372036854000123') // false isSafeNumber('0.66666666666666666667') // false isSafeNumber('9123372036854000123', { approx: true }) // false isSafeNumber('0.66666666666666666667', { approx: true }) // true
-
toSafeNumberOrThrow(value: string, config?: { approx: boolean }) : number
在安全的情况下将字符串转换为数字,否则抛出信息性错误。 -
getUnsafeNumberReason(value): UnsafeNumberReason | 未定义
当提供的“value”是不安全数字时,请描述原因:“overflow”、“underflow”、“truncate_integer”、“truncate_float”。 当值安全时返回“未定义”。 -
isLosslessNumber(值:未知):布尔值
测试一个值是否是“LosslessNumber”。
-toLosslessNumber(值:数字):LosslessNumber
将“数字”转换为“无损数字”。 当“number”超过 15 位数字的最大安全限制(因此被截断)或者为“NaN”或“Infinity”时,该函数将引发异常。
parseLosslessNumber(value: string) : LosslessNumber
“parse”使用的默认“numberParser”。 从包含数值的字符串创建“LosslessNumber”。
-parseNumberAndBigInt(值:字符串):数字| 大整数
可由“parse”使用的自定义“numberParser”。 解析器会将整数值转换为“bigint”,并将所有其他值转换为常规“number”。
-
reviveDate(键,值)
将包含 ISO 8601 日期字符串的字符串恢复为 JavaScript“Date”对象。 默认情况下,此恢复程序未打开,因为将_意外_包含日期的文本字段解析为“日期”的风险很小。reviveDate
是否可以安全使用取决于用例。 用法:从 'lossless-json' 导入 { parse, reviveDate } const data = parse('["2022-08-25T09:39:19.288Z"]', reviveDate) // 输出: // [ // 新日期('2022-08-25T09:39:19.288Z') //]
另一种解决方案是在特定的可识别对象中字符串化“Date”,例如“{‘$date’:‘2022-08-25T09:39:19.288Z’}”,并使用恢复器和替换器将此对象转换为 “日期”,反之亦然。
## 备择方案
类似的库:
- https://github.com/sidorares/json-bigint
- https://github.com/nicolasparada/js-json-bigint
- https://github.com/epoberezkin/json-source-map
## 测试
要测试该库,首先安装依赖项一次:
npm 安装
要运行单元测试:
npm测试
要构建库并运行单元测试和集成测试:
npm 运行构建和测试
棉绒
运行 linting:
npm 运行 lint
自动修复 linting 问题:
npm 运行格式
## 基准
要运行基准测试来比较与本机“JSON”解析器的性能:
npm 运行基准测试
(剧透:“lossless-json”比原生慢得多)
## 建造
要构建捆绑和缩小的库 (ES5),请首先安装依赖项一次:
npm 安装
然后捆绑代码:
npm 运行构建
这将在文件夹“./.lib”中生成 ES 模块输出和 UMD 包,可以在浏览器和 Node.js 中执行并在浏览器中使用。
### 发布
发布新版本:
$ npm 运行发布
这会:
- 棉绒
- 测试
- 建造
- 增加版本号
- 将更改推送到 git,添加 git 版本标签
- 发布npm包
要尝试构建并查看更改列表而不实际发布:
$ npm run 发布-试运行
## 执照
根据 MIT 许可证 发布。