如何使用接口使API响应具有类型安全性/声明?TypeScript

huangapple go评论65阅读模式
英文:

How to use interface to make API responses type safe/declared? TypeScript

问题

这是你的翻译结果:

export interface ContentfulApiResponse {
  sys: {
    type: string;
  };
  total: number;
  skip: number;
  limit: number;
  items: ContentfulItem[];
  includes: {
    Entry: ContentfulIncludesEntry[];
    Asset: ContentfulIncludesAsset[];
  };
}

interface ContentfulItem {
  metadata: {
    tags: [];
  };
  sys: ContentfulSys;
  fields: {
    [key: string]: any; // This allows for dynamic fields in content types
  };
}

interface ContentfulSys {
  space: {
    sys: {
      type: string;
      linkType: string;
      id: string;
    };
  };
  id: string;
  type: string;
  createdAt: string;
  updatedAt: string;
  environment: {
    sys: {
      id: string;
      type: string;
      linkType: string;
    };
  };
  revision: number;
  contentType: {
    sys: {
      type: string;
      linkType: string;
      id: string;
    };
  };
  locale: string;
}

interface ContentfulIncludesAsset {
  metadata: {
    tags: [];
  };
  sys: ContentfulSys;
  fields: {
    title: string;
    description: string;
    file: {
      url: string;
      details: {
        size: number;
        image: {
          width: number;
          height: number;
        };
      };
      fileName: string;
      contentType: string;
    };
  };
}

interface ContentfulIncludesEntry {
  metadata: {
    tags: [];
  };
  sys: ContentfulSys;
  fields: {
    countryTag?: string;
    regionTag?: string;
  };
}

请注意,我对ContentfulItemContentfulSys进行了修改,以允许动态字段在内容类型中。这样,你可以适应不同的内容类型和字段结构。

英文:

I am using TypeScript and Contentful in the same project and am having issue getting my head around how to map out the API response within an interface in a way that is clean and sensible and dynamic some sort as the API responses from contentful are massive and ugly - and for each different content type they could be different.

So these are my main issues:

  1. actually mapping out the API response in an interface - its so big and there is so much unnecessary data do I need to put it all in an interface?
  2. What do I do if say the client later down the line creates new content types so then the api response coming would be structured slightly differently and at which point the interfaces would be wrong?

All help appreciated, really just need advice down the right way to investigate the solutions.

This is an example of one of the contentful API responses:

{
"sys":{
"type":"Array"
},
"total":2,
"skip":0,
"limit":100,
"items":[
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"1w9AMDdCqRHRMEfzif3J0V",
"type":"Entry",
"createdAt":"2023-06-22T14:17:28.969Z",
"updatedAt":"2023-06-22T14:17:28.969Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"blogPost"
}
},
"locale":"en-US"
},
"fields":{
"title":"Second Test",
"body":{
"data":{
},
"content":[
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"This is a test.",
"nodeType":"text"
}
],
"nodeType":"paragraph"
}
],
"nodeType":"document"
},
"tags":[
{
"sys":{
"type":"Link",
"linkType":"Entry",
"id":"5vuJaOnrVvh34oAiBt8qgh"
}
},
{
"sys":{
"type":"Link",
"linkType":"Entry",
"id":"6feqnd9taR55ruluGBsp8h"
}
}
]
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"32eWu0P7zXgbxqum5nbdqP",
"type":"Entry",
"createdAt":"2023-06-22T14:14:34.053Z",
"updatedAt":"2023-06-22T14:14:34.053Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"blogPost"
}
},
"locale":"en-US"
},
"fields":{
"title":"Test",
"body":{
"data":{
},
"content":[
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"This is a test",
"nodeType":"text"
}
],
"nodeType":"heading-1"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"",
"nodeType":"text"
}
],
"nodeType":"paragraph"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"This is a test",
"nodeType":"text"
}
],
"nodeType":"heading-3"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"",
"nodeType":"text"
}
],
"nodeType":"paragraph"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"This is a test.",
"nodeType":"text"
}
],
"nodeType":"paragraph"
},
{
"data":{
},
"content":[
{
"data":{
},
"marks":[
],
"value":"",
"nodeType":"text"
}
],
"nodeType":"paragraph"
}
],
"nodeType":"document"
},
"tags":[
{
"sys":{
"type":"Link",
"linkType":"Entry",
"id":"63QnQZsSKUUI3TNAvsI19J"
}
},
{
"sys":{
"type":"Link",
"linkType":"Entry",
"id":"1O7OQ1YFLMshW7BwBTL3ER"
}
}
],
"images":[
{
"sys":{
"type":"Link",
"linkType":"Asset",
"id":"35LgxsKv96DyW51Uu8DrnM"
}
},
{
"sys":{
"type":"Link",
"linkType":"Asset",
"id":"4bxRR1bBIknnVzRjeqvUIr"
}
}
]
}
}
],
"includes":{
"Entry":[
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"1O7OQ1YFLMshW7BwBTL3ER",
"type":"Entry",
"createdAt":"2023-06-22T14:13:23.918Z",
"updatedAt":"2023-06-22T14:13:23.918Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"countryTag"
}
},
"locale":"en-US"
},
"fields":{
"countryTag":"usa"
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"5vuJaOnrVvh34oAiBt8qgh",
"type":"Entry",
"createdAt":"2023-06-22T10:50:36.561Z",
"updatedAt":"2023-06-22T10:50:36.561Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"countryTag"
}
},
"locale":"en-US"
},
"fields":{
"countryTag":"australia"
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"63QnQZsSKUUI3TNAvsI19J",
"type":"Entry",
"createdAt":"2023-06-22T14:13:11.295Z",
"updatedAt":"2023-06-22T14:13:11.295Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"regionTag"
}
},
"locale":"en-US"
},
"fields":{
"title":"americas"
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"6feqnd9taR55ruluGBsp8h",
"type":"Entry",
"createdAt":"2023-06-22T10:41:10.693Z",
"updatedAt":"2023-06-22T10:41:10.693Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"regionTag"
}
},
"locale":"en-US"
},
"fields":{
"title":"apac"
}
}
],
"Asset":[
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"35LgxsKv96DyW51Uu8DrnM",
"type":"Asset",
"createdAt":"2023-06-22T14:14:11.979Z",
"updatedAt":"2023-06-22T14:14:11.979Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"locale":"en-US"
},
"fields":{
"title":"Test1",
"description":"",
"file":{
"url":"//images.ctfassets.net/rikydtrxnc79/35LgxsKv96DyW51Uu8DrnM/d8a11b6dc57a416a143af10b3569a7e0/pexels-photo-3844788.jpeg",
"details":{
"size":83381,
"image":{
"width":500,
"height":667
}
},
"fileName":"pexels-photo-3844788.jpeg",
"contentType":"image/jpeg"
}
}
},
{
"metadata":{
"tags":[
]
},
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"rikydtrxnc79"
}
},
"id":"4bxRR1bBIknnVzRjeqvUIr",
"type":"Asset",
"createdAt":"2023-06-22T14:14:26.979Z",
"updatedAt":"2023-06-22T14:14:26.979Z",
"environment":{
"sys":{
"id":"master",
"type":"Link",
"linkType":"Environment"
}
},
"revision":1,
"locale":"en-US"
},
"fields":{
"title":"Test2",
"description":"",
"file":{
"url":"//images.ctfassets.net/rikydtrxnc79/4bxRR1bBIknnVzRjeqvUIr/4ffca3cdb4db84b6ee2129cf05f06cdc/premium_photo-1661964088064-dd92eaaa7dcf.jpeg",
"details":{
"size":46936,
"image":{
"width":1000,
"height":714
}
},
"fileName":"premium_photo-1661964088064-dd92eaaa7dcf.jpeg",
"contentType":"image/jpeg"
}
}
}
]
}
}

Now the items array and includes array could have different data sets in the objects so this is why I am finding it hard to wrap my head around how to solve the issue of new content types and existing interfaces/schemas not working for them.

I have already mapped it out the response like this but think its excessive?

export interface ContentfulApiResponse {
sys: {
type: string;
};
total: number;
skip: number;
limit: number;
items: [
{
metadata: {
tags: [];
};
sys: {
space: {
sys: {
type: string;
linkType: string;
id: string;
};
};
id: string;
type: string;
createdAt: string;
updatedAt: string;
environment: {
sys: {
id: string;
type: string;
linkType: string;
};
};
revision: number;
contentType: {
sys: {
type: string;
linkType: string;
id: string;
};
};
locale: string;
};
fields: {
title: string;
body: {
data: {};
content: [
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
}
];
nodeType: string;
};
tags: [
{
sys: {
type: string;
linkType: string;
id: string;
};
},
{
sys: {
type: string;
linkType: string;
id: string;
};
}
];
};
},
{
metadata: {
tags: [];
};
sys: {
space: {
sys: {
type: string;
linkType: string;
id: string;
};
};
id: string;
type: string;
createdAt: string;
updatedAt: string;
environment: {
sys: {
id: string;
type: string;
linkType: string;
};
};
revision: number;
contentType: {
sys: {
type: string;
linkType: string;
id: string;
};
};
locale: string;
};
fields: {
title: string;
body: {
data: {};
content: [
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: "";
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
},
{
data: {};
content: [
{
data: {};
marks: [];
value: string;
nodeType: string;
}
];
nodeType: string;
}
];
nodeType: string;
};
tags: [
{
sys: {
type: string;
linkType: string;
id: string;
};
},
{
sys: {
type: string;
linkType: string;
id: string;
};
}
];
images: [
{
sys: {
type: string;
linkType: string;
id: string;
};
},
{
sys: {
type: string;
linkType: string;
id: string;
};
}
];
};
}
];
includes: {
Entry: ContentfulIncludesEntry[];
Asset: ContentfulIncludesAsset[];
};
}
interface ContentfulIncludesAsset {
metadata: {
tags: [];
};
sys: {
space: {
sys: {
type: string;
linkType: string;
id: string;
};
};
id: string;
type: string;
createdAt: string;
updatedAt: string;
environment: {
sys: {
id: string;
type: string;
linkType: string;
};
};
revision: number;
locale: string;
};
fields: {
title: string;
description: string;
file: {
url: string;
details: {
size: number;
image: {
width: number;
height: number;
};
};
fileName: string;
contentType: string;
};
};
}
interface ContentfulIncludesEntry {
metadata: {
tags: [];
};
sys: {
space: {
sys: {
type: string;
linkType: string;
id: string;
};
};
id: string;
type: string;
createdAt: string;
updatedAt: string;
environment: {
sys: {
id: string;
type: string;
linkType: string;
};
};
revision: number;
contentType: {
sys: {
type: string;
linkType: string;
id: string;
};
};
locale: string;
};
fields: {
countryTag?: string;
regionTag?: string;
};
}

And bear in mind the fields object can change with every content type.

答案1

得分: 2

以下是翻译好的部分:

我的第一个想法是,你应该首先为每个属性名称创建一个通用类型。Sys、Metadata、Tag、Environment 等等,完全忽略它们可能会有循环引用的事实,这可能会阻止它们编译,因为在这个阶段,你只是想把它们全部列出并理解它们。例如:

interface Item {
  metadata?: Metadata;
  sys?: Sys;
  fields?: Fields;
}

另一个想法是,甚至不要尝试强类型化服务器响应。相反,接受响应是 unknown 类型,然后使用 zod 来断言数据为你的代码所期望的类型。这样做的一个优点是运行时断言将帮助你找到单元测试和静态类型检查根本无法发现的错误和意外情况。

不过,你确定需要自己做这一切吗?如果像 @bgschiller/contentful-typescript-codegen 这样的工具不适用,我预计你可以找到 某种 可以使用的东西?

(注意:我并不推荐使用 @bgschiller/contentful-typescript-codegen。我以前从未听说过它,只是通过在 npmjs.com 上搜索 "types/contentful" 来找到它的。)

英文:

My first thought is that you should start by creating a common type for each property name. Sys, Metadata, Tag, Environment, etc, completely ignoring the fact that they'll have circular references that might prevent them from compiling because in this phase you just want to get it all down and understand it. For example:

interface Item {
  metadata?: Metadata;
  sys?: Sys;
  fields?: Fields;
}

Another idea is to not even try to strongly type the server response. Instead, accept that the response is unknown and then use zod to assert the data into the types your code expects. An advantage to this is that run-time assertions will help you find bugs and surprises in ways that unit tests and static type checking simply can't.

However, are you sure you need to do any of this yourself? If something like @bgschiller/contentful-typescript-codegen doesn't help, I'd expect you could find something you could use out there?

(Note: I'm not recommending @bgschiller/contentful-typescript-codegen. I'd never heard of it before and only found it by searching for "types/contentful" on npmjs.com.)

huangapple
  • 本文由 发表于 2023年6月25日 22:15:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76550838.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定