使用 TypeScript 解码 JSON
Typescript 非常适合为 JavaScript 程序添加类型安全,但仅靠它本身不足以保证程序在运行时不会崩溃。
本文展示了JSON 解码器如何帮助将 TypeScript 编译时保证扩展到运行时环境。
运行时与编译时
您正在开发的应用程序是处理用户的,因此您需要创建一个User类型:
interface User {
firstName: string;
lastName: string;
picture: string;
email: string;
}
您可以使用此类型来注释/meAPI 端点结果,然后您可以对其进行各种操作,User但我们先集中关注应用程序的个人资料区域:
firstName它会显示“+”的连接lastName。firstName在“+”下方,lastName您还想显示“email.”。- 最后,您需要显示
picture用户的头像,如果用户没有头像,则显示默认头像。
会出什么问题呢?首先,User类型定义不准确,它并没有表达API 可以返回的所有User 形状
排列组合。 我们来看几个例子:
// The result has null properties
{ firstName: "John", lastName: null, picture: null, email: "john@example.com" }
// The API returned null
null
// The result has undefined properties
{ firstName: "John", lastName: "Doe", email: "john@example.com" }
// The API contract changed and the UI team wasn't notified
{ fName: "John", lName: "Doe", picture: 'pic.jpg', email: "john@example.com" }
null你可以通过使用防御性编程技术(例如语句undefined后检查)来应对这些问题,if但是,如果其他人想/me在其他地方使用结果怎么办?也许你的同事信任这个User类型,为什么不呢?那又该怎么办?我们引入了一个新的运行时错误向量。
输入 JSON 解码器
您可以使用 Json 解码器来确保给定的运行时值符合特定的编译时类型,不仅如此,它还为您提供了应用转换、故障转移等的工具。
由于Elm的出现,Json 解码器最近变得非常流行。Elm
的 JSON 解码器是该语言的核心组成部分,被广泛用于确保 JS 与 Elm 之间流畅的通信。
JSON 解码器背后的思想是,你可以将一系列基本解码器(string,,,,,... )组合成更复杂的解码器。numberbooleanobjectarray
最先进的 JSON 解码器库
市面上有很多 JSON 解码库,但我之前做研究的时候,发现有一个库格外引人注目。Daniel Van Den Eijkel开发的这个库,既保留了 Elm 解码库的原理,又符合 TypeScript 的惯用写法。
遗憾的是,该库已停止维护且未发布,所以我决定对其进行分支,加以完善,并以ts.data.json的名称将其作为 npm 包发布。
我对该库的贡献包括:文档编写、改进错误报告、单元测试、API 改进、添加一些新的解码器以及发布 npm 包。
使用JSON解码器
安装库:
npm install ts.data.json --save
解码基础知识
在实现我们自定义的User解码器之前,让我们尝试string从头到尾解码一个文件。
import { JsonDecoder } from 'ts.data.json';
console.log( JsonDecoder.string.decode('Hi!') ); // Ok({value: 'Hi!'})
完成了!🎉
解包解码器结果
正如我们在前面的例子中看到的,解码过程分为两个步骤。
- 首先,我们声明解码器
JsonDecoder.string。 - 其次,我们执行解码器,并传递一个 JavaScript 值
*.decode('Hi!'),该值返回包装在实例中的结果Ok。
为什么要将结果包装在一个Ok实例中?因为如果失败,我们需要将结果包装在一个Err实例中。
让我们看看decode()函数签名是什么样的:
decode(json: any): Result<a>
Result<a>Ok是and的联合类型Err。
type Result<a> = Ok<a> | Err;
所以大多数情况下我们不会使用 `<T>` decode(),而是会使用 `<T>` decodePromise()。
让我们看看decodePromise()签名是什么样的:
decodePromise<b>(json: any): Promise<a>
让我们尝试string使用以下命令从头到尾解码一个字符串decodePromise():
import { JsonDecoder } from 'ts.data.json';
const json = Math.random() > 0.5 ? 'Hi!' : null;
JsonDecoder.string.decodePromise(json)
.then(value => {
console.log(value);
})
.catch(error => {
console.log(error);
});
一半时间我们会走这条then()路线并得到Hi!,另一半时间我们会走这条catch()路线得到null is not a valid string。
既然我们已经了解了基础知识,那就让我们认真起来,构建我们自己的自定义User解码器吧。
User解码器
除了原始解码器之外:
JsonDecoder.string: Decoder<string>JsonDecoder.number: Decoder<number>JsonDecoder.boolean: Decoder<boolean>
还有其他更复杂的解码器,User我们将使用JsonDecoder.object以下解码器:
JsonDecoder.object<a>(decoders: DecoderObject<a>, decoderName: string): Decoder<a>
Decoder<a>所有解码器都返回的那个东西是什么?解码器拥有解码特定值的逻辑,但它们不知道如何执行该值,这就是该类的
Decoder作用。Decoder<a>它包含用于执行、解包、链接和转换解码器/解码器值的方法。
让我们尝试User运用目前为止学到的所有技巧,从头到尾解码一个文本:
import { JsonDecoder } from 'ts.data.json';
interface User {
firstName: string;
lastName: string;
}
const userDecoder = JsonDecoder.object<User>(
{
firstName: JsonDecoder.string,
lastName: JsonDecoder.string
},
'User'
);
const validUser = {
firstName: 'Nils',
lastName: 'Frahm'
};
const invalidUser = {
firstName: null,
lastName: 'Wagner'
};
const json = Math.random() > 0.5 ? validUser : invalidUser;
userDecoder
.decodePromise(json)
.then(value => {
console.log(value);
})
.catch(error => {
console.log(error);
});
一半时间我们会得到{firstName: "Nils", lastName: "Frahm"},另一半时间我们会得到<User> decoder failed at key "firstName" with error: null is not a valid string……JsonDecoder能满足我们的需求。
掉进兔子洞
我们才刚刚触及这个库功能的冰山一角,它几乎包含了你能想到的所有类型的解码器。你还可以解码:
- 数组
- 字典
- 递归数据结构
- 无效的
- 不明确的
以及其他一些花哨的东西。
去GitHub代码库看看吧!
文章来源:https://dev.to/joanllenas/decoding-json-with-typescript-1jjc
