从 typescript 中的 API 响应正确转换日期

Posted

技术标签:

【中文标题】从 typescript 中的 API 响应正确转换日期【英文标题】:Casting dates properly from an API response in typescript 【发布时间】:2021-04-17 21:11:59 【问题描述】:

我有一个 REST API,它提供以下格式的数据:-

"id": 1, "name": "New event", "date": "2020-11-14T18:02:00"

我的前端 React 应用程序中的界面如下:-

export interface MyEvent 
  id: number;
  name: string;
  date: Date;

我使用 axios 从 API 获取响应。

const response = await axios.get<MyEvent>("/event/1");
const data = response.data;

但是,由于打字稿的限制,data.date 仍然是一个字符串。

这可能会导致代码后面出现问题,我希望所有此类日期字段都是实际的 Date 对象。

我可能会这样做:data.date = new Date(data.date);。但由于很多原因,这不是可行的方法。

有没有更好的方法来处理打字稿中的日期?一般来说,您如何处理来自 API 的日期作为响应?

【问题讨论】:

由于 JSON 而不是 TypeScript 的限制,日期是一个字符串。您可以使用字符串来表示 JSON 中的日期,也可以使用数字(自纪元以来的记号)。无论哪种方式,您都必须将 JSON 表示形式转换为实际的 Date 对象。 我知道。我提到的限制是 typescript 从不验证类型或在运行时正确转换它们。我基本上是在寻找一种以干净的方式处理此问题的方法,而无需将字符串转换为分散在我的服务层中的日期逻辑。 你试过moment.js了吗? 看看“axios 响应拦截器”。我没有使用 axios 的经验,但拦截器是标准的设计模式,快速谷歌表明 Axios 使用相同的模式。您在发出请求之前注册拦截器,然后拦截器在响应 JSON 中查找与日期匹配的字符串。我为 Restangular 做了类似的事情,但很抱歉,我无法共享代码(无论如何它是用于不同的库)。 @Babri 我的解决方案与this 非常相似。查看Angular Http Interceptor 部分。它递归地遍历对象结构,寻找与 ISO-8601 日期格式匹配的字符串值,并用日期对象替换匹配项。这需要适应 Axios,但我希望这会有所帮助。干杯。 【参考方案1】:

所以最适合我用例的解决方案类似于@Amy 在原始问题的评论中提到的解决方案。

所以我使用 Axios 拦截器将日期转换为 Date 对象。

const client = axios.create( baseURL: process.env.NEXT_PUBLIC_API_BASE_URL );

client.interceptors.response.use(originalResponse => 
  handleDates(originalResponse.data);
  return originalResponse;
);

export default client;

为了识别日期,我使用了正则表达式,为了转换为 Date 对象,我使用了 date-fns 包。

const isoDateFormat = /^\d4-\d2-\d2T\d2:\d2:\d2(?:\.\d*)?$/;

function isIsoDateString(value: any): boolean 
  return value && typeof value === "string" && isoDateFormat.test(value);


export function handleDates(body: any) 
  if (body === null || body === undefined || typeof body !== "object")
    return body;

  for (const key of Object.keys(body)) 
    const value = body[key];
    if (isIsoDateString(value)) body[key] = parseISO(value);
    else if (typeof value === "object") handleDates(value);
  

【讨论】:

我找到了更好的正则表达式here: ^(\d4)-(\d2)-(\d2)T(\d2):(\d2):(\d2(?:\.\d*)?)((-(\d2):(\d2)|Z)?)$ 正确的正则表达式是^\d4-\d2-\d2T\d2:\d2:\d2(?:\.\d*)?(?:[-+]\d2:?\d2|Z)?$ 单元测试可以通过regex101.com/r/Xpseru/2 显示此外,如果您只是测试字符串的有效性,则不需要所有捕获的分组。【参考方案2】:

我个人使用的一个选项是创建一个实用函数,将输入从any 解析为MyEvent。你可以这样做:

function createMyEvent(data: any): MyEvent 
    return 
        id: data.id,
        name: data.name,
        date: new Date(data.date)
    ;

然后像这样使用它:

// Same as before, but without a type specified.
const response = await axios.get("/event/1");
// Type is still "MyEvent", but this time its valid.
const data = createMyEvent(response.data);

为了使其完全类型安全,该函数还应验证输入对象,以验证其具有正确格式的正确字段。如果您担心 API 可能会更改 MyEvent 的架构,而无需对客户端进行匹配更改,这一点尤其重要。

如果您在启用了noExplicitAny 的代码库中工作,您还可以根据需要使用Record&lt;string, unknown&gt; 而不是any。这两种方法都需要在createMyEvent 中添加额外的类型断言,但基本思想保持不变。

【讨论】:

以上是关于从 typescript 中的 API 响应正确转换日期的主要内容,如果未能解决你的问题,请参考以下文章

使用 Typescript 扩展快速响应对象的正确方法

如何将 fetch API 响应转换为 TypeScript 中的特定类型?

我期望从 API 响应中接收一组对象,但在使用 TypeScript 的 React 中获得一组 Promise

C# 中的 Google 语音转文本 API

如何使用 TypeScript 验证带有 Axios GET 响应的接口

React.js 和 Typescript 返回获取 API 响应的类型?