你用的 JSON.parse 真的舒服吗?来简单封装一层吧!

2,671 阅读8分钟

扯皮

JSON.parse 我们都不陌生,大白话就是用来解析 JSON 字符串的,但是站在用户的角度来讲可没法保证一定传进来的就是 JSON 格式的字符串,所以针对于不规范的字符串贴心的 JSON.parse 会给我们抛出异常

所以一般情况下在使用它时都需要使用 try...catch 进行包裹,像我们公司校验比较严格的项目针对于 JSON.parse 会进行检测,如果没有使用 try...catch 你连代码都提交不上去🙃

但是吧,在业务代码中充斥这么多的 try...catch 也是比较头疼的🤔,所以就有同学想到干脆封装一层 JSON.parse 来避免业务代码过于臃肿,今天来写篇小短文谈谈关于它的二次封装以及一些业务场景的适配

正文

“包一层”

所谓的包一层就真的是简单的包一层,在我们项目中通常是因为没有安装公共的 utils 包但又想避免上述描述的情况才会这样简单粗暴:

Snap.png

先别急着笑,经过我初步排查公司内有些项目就是这样写的😊,原因也基本上都是我上面所说的那样

我们来简单分析一下吧,它的作用其实就是省掉了业务代码里的 try...catch,而针对于异常情况提供了一个配置参数 errorMessage 来允许用户自定义其内容,最后通过返回值取得,然后就没有了...

只能说也够用🤔,毕竟大多数场景下我们只是需要一个兜底操作罢了,但是真正使用起来你会发现这种写法针对于特殊场景用起来会十分鸡肋,而且过于简陋,够用只是因为没涉及到相应的业务,我们慢慢往下完善

类型判断和返回值

实际上 JSON.parse 并不是只能传入字符串,别看我们安装 TS 后内置的类型提示文件写着只有 string 类型

image.png

我们来看这几个例子:

image.png

所以说要封装的 safeJsonParse 严谨一些可能还需要对传入的 text 进行类型判断,当然这点就根据团队和个人风格来决定了,比如我们公司封装的 safeJsonParse 内部还进行了 typeof 判断走不同的处理逻辑

我们这里还是限制死为 string 类型吧,只是给大伙看一眼 JSON.parse 传入不同类型的样子🤪,特别是传入 "null" 的返回值为 null,这点还是需要注意下的:

Snap.png

问题来了,为什么参数已经写了 string 类型内部还要通过 typeof 判断一次处理呢?因为 TS 毕竟只是编译阶段的报错提示,如果我强制给你传入非 string 你又奈我何😆,无非是多几个爆红罢了

PS:这在我们项目中可太常见了,清一色的 TS 爆红文件,甚至都不愿意用 any 来敷衍🤣

更何况如果该方法在纯 JS 环境下使用的话就更无法进行类型限制了,所以一般封装的工具函数都要对入参有严格的类型判断,且不依赖类型校验工具

思考一下上面的写法还是太过于简陋了🤔,比如针对于 try...catch 拿到的 error 仅仅是打印了出来,虽然已经支持用户自定义报错的信息并将其返回了,但假如用户还是想要拿到 catch 里的 error 呢?

所以我们修改一下第二个参数配置项,可以以回调函数的方式将 error 抛出去,并给 errorMessage 默认值:

Snap.png

这样用户使用起来就会方便些,想拿错误信息传回调函数就可以了🧐

还有一个问题,目前我们这里的返回值都是 any:

image.png

仔细想想我们作为封装工具的人也无法确定 parse 后的结果,所以应该交给用户来决定,那这里就有两个解法了:

  1. 使用泛型:

Snap.png

但是用泛型会有一个问题,由于 errorMessage 的存在会导致最终结果是一个联合类型,所以建议使用第二个解法

image.png

  1. 统一返回 unknown 类型,用户想要使用必须手动 as 确定类型

Snap.png

和泛型类似都需要用户决定具体类型,只不过我们把上面由于 errorMessage 导致的联合类型问题抛给了用户,就减少我们自己封装的心智负担辣😆

image.png

以上实现的思路和使用方式基本上已经和我们公司内部封装的大差不差了,只不过公司里封装的细节会再多一点,但写这篇文章并不是只分享上面这一点代码的

实际上真正写这篇文章的原因是因为在我们的 CR 会上使用了公司内部的 safeJsonParse,在一个业务场景中用起来十分鸡肋,所以想记录一下并给他们提点建议

增加 “仅判断” 场景

在我们的 CR 会上有位同学的业务代码是这样写的,业务场景就是一个普通的 textArea 表单,针对于输入内容需要校验其为 JSON 字符串格式,其中使用的 safeJsonParse 就是我们上面封装的方法:

Snap.png

乍一看这段代码考虑的还挺严谨的,不仅对返回值进行了判断又使用了 try...catch 进行包裹,基本兜底了全部报错

但真的是这样嘛?🤔 仔细回想下我们的 safeJsonParse 实现就能发现一些问题

比如内部其实已经做了 try...catch,你的外部 try...catch 基本上就不会再走到 catch 层了,如果走到了那就说明封装的有问题了🤪

再来看通过 typeof 判断返回值为 object 类型作为判断依据,别忘了一开始我们强调的这个特殊值:

image.png

所以真要按照封装者的意图使用的话,这段代码应该这样写:

Snap.png

是不是顿时感觉这里的 safeJsonParse 用起来特别鸡肋🙃,我其实就是想要校验一下这里的 value 是否符合规范,结果使用起来还这么麻烦,而且据我初步观察其实很多人都不知道 safeJsonParse 的第二个参数的用法...有点好奇心的人可能会直接点进去看传参类型,也有的会直接查文档,当然肯定也会有还没用到的情况

所以最终讨论的结果是丢掉 safeJsonParse,改用普通 JSON.parse 并在业务代码中进行 try...catch

这就说明了一个问题: safeJsonParse 存在缺陷,一些业务场景并没有覆盖到,我们现在来改造一下:

Snap.png

我们重点修改了返回值,不再直接返回一个 value,而是返回一个对象,这样做虽然是属于对原来封装的 safeJsonParse 进行破坏性改动,但已经完美覆盖到了上面的业务场景:

Snap.png

这种使用方式体验可比纯 try...catch 和回调的形式好太多了🧐

只想要判断?isValid 满足你,什么?还想要错误信息?那 err 也给你,最后再把 value 也一块给你,想用就啥解构啥,完美

关于第二个参数

考虑到这些其实就已经差不多了,但那天我突然又想到自己的低代码毕设🤔,当时有一个业务场景是需要将配置字符串转换为对象,自己也封装了 JSON.parse 方法,与上面不同的是我用到了它的第二个参数...

关于第二个参数其实我有一篇文章里也有介绍过,其实还是比较重要的:JSON.parse、stringify 封装:弥补实现深拷贝的所有缺陷!- 掘金

主要的业务场景是因为如果针对于函数的 JSON.parse 是无法解析出来的,但也并不全是 JSON.parse 的锅,而是 JSON.stringify ,它把函数搞丢了:

image.png

所以上面的业务场景实际上就是需要自己封装 JSON.parseJSON.stringify,让它来支持函数的解析,这都需要用到它们的第二个参数 reviver,这里的流程之前的文章都有讲过就不细讲了,可以自己下去摸索一下,总之想要表达的意思是第二个参数在某种业务场景下还是会用到的,所以最好还是加上吧:

Snap.png

End

以上就是这篇小短文的全部内容了,代码虽然比较简单,但是也代表了在一些真实业务场景中的思考

之前我觉得在面试过程中只要做的东西有难度就能够吸引面试官,能够凸显出自己的水平。但真的工作几个月之后会发现最普通的业务它也有值得思考的点,有时候与他人引起共鸣比自己单人吟唱的效果要好很多