发布于 2026-01-06 0 阅读
0

使用 TypeScript 解码 JSON

使用 TypeScript 解码 JSON

Typescript 非常适合为 JavaScript 程序添加类型安全,但仅靠它本身不足以保证程序在运行时不会崩溃。

本文展示了JSON 解码器如何帮助将 TypeScript 编译时保证扩展到运行时环境。

运行时与编译时

您正在开发的应用程序是处理用户的,因此您需要创建一个User类型:

interface User {
   firstName: string;
   lastName: string;
   picture: string;
   email: string;
}
Enter fullscreen mode Exit fullscreen mode

您可以使用此类型来注释/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" }
Enter fullscreen mode Exit fullscreen mode

替代文字

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
Enter fullscreen mode Exit fullscreen mode

解码基础知识

在实现我们自定义的User解码器之前,让我们尝试string从头到尾解码一个文件。

import { JsonDecoder } from 'ts.data.json';

console.log( JsonDecoder.string.decode('Hi!') ); // Ok({value: 'Hi!'})
Enter fullscreen mode Exit fullscreen mode

完成了!🎉

解包解码器结果

正如我们在前面的例子中看到的,解码过程分为两个步骤。

  • 首先,我们声明解码器JsonDecoder.string
  • 其次,我们执行解码器,并传递一个 JavaScript 值*.decode('Hi!'),该值返回包装在实例中的结果Ok

为什么要将结果包装在一个Ok实例中?因为如果失败,我们需要将结果包装在一个Err实例中。
让我们看看decode()函数签名是什么样的:

decode(json: any): Result<a>
Enter fullscreen mode Exit fullscreen mode

Result<a>Ok是and的联合类型Err

type Result<a> = Ok<a> | Err;
Enter fullscreen mode Exit fullscreen mode

所以大多数情况下我们不会使用 `<T>` decode(),而是会使用 `<T>` decodePromise()
让我们看看decodePromise()签名是什么样的:

decodePromise<b>(json: any): Promise<a>
Enter fullscreen mode Exit fullscreen mode

让我们尝试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);
  });
Enter fullscreen mode Exit fullscreen mode

一半时间我们会走这条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);
  });
Enter fullscreen mode Exit fullscreen mode

一半时间我们会得到{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