处理TS类型声明文件,保留指定key的类型声明

2024-09-15 12:50:42 浏览数 (2)

我的原始需求是这样的,写了一个nodejs命令行工具,工具的功能是,拉取后端接口导出的 postman.json 接口内容,通过接口中的入参出参数据,生成入参出参的TS类型声明文件,达到在ts业务代码中可以校验接口入参和出参类型的目的,

postman.json的大致格式如下

代码语言:javascript复制
{
  "item": [
    {
      "item": [
        {
          "item": [],
          "name": "WechatMiniAppNatureController",
          "description": "WechatMiniAppNatureController"
        }
      ],
      "name": "wpe-miniwe-recycle-srv-api",
      "description": "exported at 2024-09-09 15:17:40"
    },
    {
      "item": [
        {
          "item": [
            {
              "request": {
                "method": "POST",
                "description": "",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json",
                    "type": "text",
                    "description": ""
                  }
                ],
                "body": {
                  "mode": "raw",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  },
                  "raw": "{n  "Id": 0n}"
                },
                "url": {
                  "path": [
                    "wechat",
                    "rec",
                    "v1",
                    "nature",
                    "apply",
                    "taxRebateInfo"
                  ],
                  "query": [],
                  "host": "{{wpe-miniwe-recycle-srv-web}}",
                  "raw": "{{wpe-miniwe-recycle-srv-web}}/wechat/rec/v1/nature/apply/taxRebateInfo"
                }
              },
              "response": [
                {
                  "name": "退税申请详情接口-Example",
                  "originalRequest": {
                    "method": "POST",
                    "description": "",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json",
                        "type": "text",
                        "description": ""
                      }
                    ],
                    "body": {
                      "mode": "raw",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      },
                      "raw": "{n    "Id": 0n}"
                    },
                    "url": {
                      "path": [
                        "wechat",
                        "rec",
                        "v1",
                        "nature",
                        "apply",
                        "taxRebateInfo"
                      ],
                      "query": [],
                      "host": "{{wpe-miniwe-recycle-srv-web}}",
                      "raw": "{{wpe-miniwe-recycle-srv-web}}/wechat/rec/v1/nature/apply/taxRebateInfo"
                    }
                  },
                  "code": 200,
                  "_postman_previewlanguage": "json",
                  "header": [
                    {
                      "name": "date",
                      "key": "date",
                      "value": "周一, 09 9月 202415:17:40 GMT",
                      "description": "The date and time that the message was sent"
                    }
                  ],
                  "body": "{n    "Response": {n        "RequestId": "",n        "Error": {n            "Code": "",n            "Message": ""n        },n        "Data": {n            "id": 0,n            "taxRebateNumber": "", //退税申请序号n            "natureRecordId": "", //自然人档案号n            "registryNumber": "", //登记序号n            "collectionItemCode": "", //征收项目代码n            "collectionItemName": "", //征收项目名称n            "collectionCode": "", //征收品目代码n            "taxBureauCode": "", //主管税务所科分局代码n            "taxAuthorityCode": "", //主管税务机关代码n            "taxAmountAuthorityCode": "", //税款所属税务机关代码n            "taxAmountAuthorityName": "", //税款所属税务机关名称n            "streetTownCode": "", //街道乡镇代码n            "taxAmount": 0.0, //应退税额n            "taxRate": 0.0, //税率n            "taxUuid": "", //税票UUIDn            "eleTaxNumber": "", //电子税票号码n            "taxStartTime": "", //税款所属期起n            "taxEndTime": "", //税款所属期止n            "applyStatus": 0,n            "rebateStatus": "",n            "rebateStatusCn": "",n            "taxStatus": 0,n            "rejectReason": "",n            "bankCardNo": "",n            "createTime": ""n        }n    }n}"
                }
              ],
              "name": "退税申请详情接口"
            }
          ],
          "name": "WechatMiniAppTaxController",
          "description": "WechatMiniAppTaxController"
        },
        {
          "item": [
            {
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json",
                    "type": "text",
                    "description": ""
                  }
                ],
                "body": {
                  "mode": "raw",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  },
                  "raw": "{n  "QrId": ""n}"
                },
                "url": {
                  "path": [
                    "wechat",
                    "rec",
                    "v1",
                    "operator",
                    "inviteConfirm"
                  ],
                  "query": [],
                  "host": "{{wpe-miniwe-recycle-srv-web}}",
                  "raw": "{{wpe-miniwe-recycle-srv-web}}/wechat/rec/v1/operator/inviteConfirm"
                }
              },
              "response": [
                {
                  "name": "operatorInviteConfirm-Example",
                  "originalRequest": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json",
                        "type": "text",
                        "description": ""
                      }
                    ],
                    "body": {
                      "mode": "raw",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      },
                      "raw": "{n    "QrId": ""n}"
                    },
                    "url": {
                      "path": [
                        "wechat",
                        "rec",
                        "v1",
                        "operator",
                        "inviteConfirm"
                      ],
                      "query": [],
                      "host": "{{wpe-miniwe-recycle-srv-web}}",
                      "raw": "{{wpe-miniwe-recycle-srv-web}}/wechat/rec/v1/operator/inviteConfirm"
                    }
                  },
                  "code": 200,
                  "_postman_previewlanguage": "json",
                  "header": [
                    {
                      "name": "date",
                      "key": "date",
                      "value": "周一, 09 9月 202415:17:40 GMT",
                      "description": "The date and time that the message was sent"
                    }
                  ],
                  "body": "{n    "Response": {n        "RequestId": "",n        "Error": {n            "Code": "",n            "Message": ""n        },n        "Data": falsen    }n}"
                }
              ],
              "name": "operatorInviteConfirm"
            }
          ],
          "name": "WechatMiniAppOperatorController",
          "description": "WechatMiniAppOperatorController"
        }
      ],
      "name": "wpe-miniwe-recycle-srv-web",
      "description": "exported at 2024-09-09 15:17:40"
    }
  ],
  "info": {
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "name": "wpe-miniwe-recycle-srv-20240909151740",
    "description": "exported at 2024-09-09 15:17:40"
  }
}

上面的接口文档中 接口地址为 /wechat/rec/v1/nature/apply/taxRebateInfo 的接口返回体,经过数据反解析后输出的Ts类型声明代码为

代码语言:javascript复制
export type TWechatRecV1NatureApplyTaxRebateInfoRess = {
  Code: number;
  Error: {
    Code: number;
    Message: string;
  },
  RequestId: string;
  Data: {
    id: number;
    taxRebateNumber: string;
    natureRecordId: string;
    // ... 其他属性
  }
};

上面的代码中,除了Data内的数据,其他的都是所有接口相同的属性内容,所以我需要处理生成的ts文件,只保留 Data 的类型描述。

也尝试过好几种方案,主要有

  • 从源代码处理,在postman文件的response -> body 代码中处理完内容再去做反解析
  • 在生成的文件中通过字符串匹配去查找

上面的方法中,主要都存在一个问题,就是postman源代码中,body包含了很多杂七杂八的内容,比如换行符,注释,还有转译字符,加上body内容的层级是不固定,这为我们做正则匹配带来了很多麻烦,所以兜兜转转想到了最终的解决方案,那就是使用AST的方法来处理生成的TS代码,这样在操作AST的过程中,babel 会帮我们处理好注释和其他不相关的内容。先show一下最终的代码

代码语言:javascript复制
// 要先安装下面的依赖
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
/**
 * 取出声明代码中指定key的interface代码
 * 使用babel的抽象语法树转换,处理,生成代码
 * @param typeCode ts代码
 * @param rootName 根类型名
 * @param keyName 属性名
 * @returns 取出的代码
 */
export const subInterfaceByKey = (typeCode: string, rootName: string, keyName: string): string => {
  // 从ts类型声明代码中取出指定key的interface代码,现将代码转换成ast
  const ast = parser.parse(typeCode, { sourceType: 'module', plugins: ['typescript'] });
  let titleType = 'any';
  // 从ast中找到指定key的接口,生成代码返回。
  traverse(ast, {
    TSInterfaceDeclaration(path) {
      if (path.node.id.name === rootName) {
        const properties = path.node.body.body;
        const titleProperty = properties.find(prop => prop.key.name === keyName);
        // console.log(titleProperty.typeAnnotation);
        if (titleProperty) {
          titleType = generate(titleProperty.typeAnnotation).code;
        }
      }
    },
  });
  if (titleType !== 'any') {
    // 去掉类型前面的冒号和空格
    return titleType.replace(/:s/, '');
  }
  return titleType;
};

上面的代码中,通过babel parser将要处理的代码转换成ast,然后通过 traverse 的 TSInterfaceDeclaration 勾子来处理ts interface 类型的代码,最后将处理后的代码生成好赋值给变量返回,

处理后的声明文件内容就成了下面这个样子了

代码语言:javascript复制
export type TWechatRecV1NatureApplyTaxRebateInfoRess = {
  id: number;
  taxRebateNumber: string;
  natureRecordId: string;
  // 。。。其他属性
};

至于为什么要用type而不是interface,原因是,Data 数据有可能不是一个对象,而是基础数据类型或数组,比如

代码语言:javascript复制
export type TWechatRecV1JodCancelCmbcBillRess = boolean;

经过这次经验,我想以后再遇到这种代码处理的需求,我不会第一时间想到通过正则来处理,而是通过AST来处理了,即安全又逻辑清晰,

0 人点赞