现在有这么一个需求:后端返回 list 给你,id 是一个 20 位数的 int 类型,在 js 里叫做 number 类型,你需要用 js 变量存储该数据,在编辑数据时候传入该 id,并且后端强校验必须传入数字类型。

坑 1:
首先如果你不做任何处理直接去 getlist,比如如下代码:

1
2
3
4
function getList() {
return [{ id: 12312312312312312312 }]
}
const list = getList() // 此时list[0].id是什么?答案是12312312312312312000而不是12312312312312312312

这是因为 js 的 number 类型有个最大值(安全值),只能存储 2^53 内的数据,这个值是 9007199254740992,而我们的 12312312312312312312 很明显是大于这个安全值的。

那么该怎么办呢?可以使用 json-bigint 库(后面或许会考虑自己实现这么一个函数,先不纠结这个),这样拿到的数据就是字符串”12312312312312312312”,这样起码你能用变量存储这么一串数据了,虽然他目前变成了字符串:

1
const list = [{ id: '12312312312312312312' }]

至此,我们只是做到了 拿到数据并且正确存储数据,接下来是如何正确把该数据用 number 类型通过后端接口强校验,这里就涉及到坑 2 了:

我最开始的需求说了,后端 tm 强校验,必须传 number,不能传 string 的 id,你该怎么办?

我们先来说 fetch 请求,一般来说,你传的 body 其实可以是一个 typeof object 类型,而不是一个序列化后的 string 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 直接传一个js对象 typeof 为object类型,是允许的:
fetch(url, {
method: 'POST',
body: {
id: 123,
},
})

// 但其实下面才是比较合理的使用方式,转成json string:

fetch(url, {
method: 'POST',
body: JSON.stringify({
id: 123,
}),
})

回到需求,我们的 id 是 12312312312312312312,假设我们这样传:

1
2
3
4
5
6
fetch(url, {
method: 'POST',
body: {
id: 12312312312312312312,
},
})

嘿嘿,你是不是以为这样不行,这样失去精度之类的…..其实上面是可以的…..但是,但是,但是!上面的代码是写死的数据,并不是把这个 id 先赋值给了 js 变量,再传入该变量。这里是直接写死的。对于 fetch 来说,该请求的 body 最终会解析成一个序列化 json 字符串,所以如果你在代码里面写死一个很大的死数据,其实是没问题的。

但下面这种方式,就会失去精度,导致传入的 id 不对:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const id = 12312312312312312312
fetch(url, {
method: 'POST',
body: {
id,
},
})

// 上面的请求的实际效果如下:

fetch(url, {
method: 'POST',
body: {
id: 12312312312312312000,
},
})

可以发现,你最终传入了一个错误的 id 给接口,这会导致你这次的操作失败。

那么该如何做,才能保证可以通过后端接口的强校验呢?(事实上,我个人认为这种长度的 id 本就不应该用 int,其次如果你后端真的用了 int,也应该允许前端传入字符串,不该做强校验。)

答案是转成 json 序列化再传。假设你已经用 json-bigint 库成功存储了这条数据:

1
2
3
const item = {
id: '12312312312312312312', // 注意 id 的 value 是 string 类型。
}

此时我们肯定不能直接用 JSON.stringify(item)传给后端,这样是没办法通过他的校验的。

此时序列化后的 json 字符串是:

1
const str_item = '{"id":"12312312312312312312"}'

而我们要做的最关键的一步,就是把这个 str_item 变成:

1
const str_item2 = '{"id":12312312312312312312}'

发现区别了吗?value 里面的引号没有了,意味着从 string 变成 number 的。

但是这里不能用 JSON.parse 转对象后再 Number(id),因为这个操作又会导致失去精度,所以我们只能全程操作字符串。

而这里的实现方式就各不相同了,比如你可以写个满足你规则的正则去替换掉里面的引号。

如果是复杂对象数组,我的建议是每层单独处理,比如该对象数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 此时的list已经是后端返回后 用json-bigint 库转成字符串了的:
const list = [
{
pid: '123123123123123123121',
children: [
{
cid: '123123123123123123122',
},
{
cid: '123123123123123123123',
},
],
},
]

那么 str_list 应该是:’[{“pid”:”123123123123123123121”,”children”:[{“cid”:”123123123123123123122”},{“cid”:”123123123123123123123”}]}]’

我们要做的是把每个 pid 和 children 里面的 cid 的 value 的引号去掉,那么可以这么写:

1
2
3
str_list
.replace(/"pid":"(\d+)"/g, '"pid":$1')
.replace(/"cid":"(\d+)"/g, '"cid":$1')

此时 str_list 就变成了:’[{“pid”:123123123123123123121,”children”:[{“cid”:123123123123123123122},{“cid”:123123123123123123123}]}]’

这时候的数据是不能 JSON.parse 的,会失去精度,但是你可以直接把这段字符串传给接口,后端那边拿到你这段字符串,parse 后是能正确拿到该 id 的(拿不到的话 100%是后端技术问题)

所以总结一下,假如后端返回这条数据给你:

1
2
3
function getList() {
return [{ id: 123123123123123123121 }]
}

那么你需要用 json-bigint 库,拿到转成字符串且不丢失精度后的结果:

1
2
3
4
5
const list = [
{
id: '123123123123123123121',
},
]

然后 JSON.stringify 变成:’[{“id”:”123123123123123123121”}]’
然后把该结果的 value 的字符串删除,变成:’[{“id”:123123123123123123121}]’

然后请求接口:

1
2
3
4
const str_body = '[{"id":123123123123123123121}]'
fetch(url, {
body: str_body,
})

大功告成。