如何将 json 转换为打字稿接口?
How to convert a json to a typescript interface?
给定 api 的 JSON 输出:
{
"id": 13,
"name": "horst",
"cars": [{
"brand": "VW",
"maxSpeed": 120,
"isWastingGazoline": true
}]
}
我想为打字稿定义接口:
export interface Car {
brand: string;
maxSpeed: number;
isWastingGazoline: boolean;
}
export interface RaceCarDriver {
id: number;
name: string;
cars: Car[];
}
但我不希望他们手动输入它们,我宁愿让脚本为我生成它们。
这样的网络服务
您可以使用 npm 模块代替网络托管解决方案:
https://www.npmjs.com/package/json-schema-to-typescript
如果你的 JSON 来自 HTTP API 并且 API 有一个 swagger 代码定义,你可以生成一个 TypeScript 客户端:
https://github.com/swagger-api/swagger-codegen#api-clients
如果您 json 来自 Java ot .Net 支持,您可以从 Java 或 C# 类:
生成 TypeScript
您可以使用 typescript compiler API 及其推断类型的能力编写脚本。我真的很惊讶它是多么容易。
您必须包装您的示例数据以使其可编译为打字稿代码。该脚本将选择所有变量声明并尝试为它们打印推断类型。它使用变量名和 属性 名称来为类型分配名称,如果两个对象具有相同名称的 属性,它将从第一个对象中选择类型。因此,如果这些类型实际上不同,它将不起作用(修复留作练习)。对于您的 JSON 输出,数据样本将类似于
文件sample.ts
let raceCarDriver = {
"id": 13,
"name": "horst",
"cars": [{
"brand": "VW",
"maxSpeed": 120,
"isWastingGazoline": true,
}]
};
脚本已使用 Typescript 2.1(刚刚发布)进行测试:
npm i typescript
npm i @types/node
./node_modules/.bin/tsc --lib es6 print-inferred-types.ts
node print-inferred-types.js sample.ts
输出:
export interface RaceCarDriver {
id: number;
name: string;
cars: Car[];
}
export interface Car {
brand: string;
maxSpeed: number;
isWastingGazoline: boolean;
}
这是脚本:print-inferred-types.ts
:
import * as ts from "typescript";
let fileName = process.argv[2];
function printInferredTypes(fileNames: string[], options: ts.CompilerOptions): void {
let program = ts.createProgram(fileNames, options);
let checker = program.getTypeChecker();
let knownTypes: {[name: string]: boolean} = {};
let pendingTypes: {name: string, symbol: ts.Symbol}[] = [];
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile.fileName == fileName) {
ts.forEachChild(sourceFile, visit);
}
}
while (pendingTypes.length > 0) {
let pendingType = pendingTypes.shift();
printJsonType(pendingType.name, pendingType.symbol);
}
function visit(node: ts.Node) {
if (node.kind == ts.SyntaxKind.VariableStatement) {
(<ts.VariableStatement>node).declarationList.declarations.forEach(declaration => {
if (declaration.name.kind == ts.SyntaxKind.Identifier) {
let identifier = <ts.Identifier>declaration.name;
let symbol = checker.getSymbolAtLocation(identifier);
if (symbol) {
let t = checker.getTypeOfSymbolAtLocation(symbol, identifier);
if (t && t.symbol) {
pendingTypes.push({name: identifier.text, symbol: t.symbol});
}
}
}
});
}
}
function printJsonType(name: string, symbol: ts.Symbol) {
if (symbol.members) {
console.log(`export interface ${capitalize(name)} {`);
Object.keys(symbol.members).forEach(k => {
let member = symbol.members[k];
let typeName = null;
if (member.declarations[0]) {
let memberType = checker.getTypeOfSymbolAtLocation(member, member.declarations[0]);
if (memberType) {
typeName = getMemberTypeName(k, memberType);
}
}
if (!typeName) {
console.log(`// Sorry, could not get type name for ${k}!`);
} else {
console.log(` ${k}: ${typeName};`);
}
});
console.log(`}`);
}
}
function getMemberTypeName(memberName: string, memberType: ts.Type): string | null {
if (memberType.flags == ts.TypeFlags.String) {
return 'string';
} else if (memberType.flags == ts.TypeFlags.Number) {
return 'number';
} else if (0 !== (memberType.flags & ts.TypeFlags.Boolean)) {
return 'boolean';
} else if (memberType.symbol) {
if (memberType.symbol.name == 'Array' && (<ts.TypeReference>memberType).typeArguments) {
let elementType = (<ts.TypeReference>memberType).typeArguments[0];
if (elementType && elementType.symbol) {
let elementTypeName = capitalize(stripS(memberName));
if (!knownTypes[elementTypeName]) {
knownTypes[elementTypeName] = true;
pendingTypes.push({name: elementTypeName, symbol: elementType.symbol});
}
return `${elementTypeName}[]`;
}
} else if (memberType.symbol.name == '__object') {
let typeName = capitalize(memberName);
if (!knownTypes[typeName]) {
knownTypes[typeName] = true;
pendingTypes.push({name: typeName, symbol: memberType.symbol});
}
return typeName;
} else {
return null;
}
} else {
return null;
}
}
function capitalize(n: string) {
return n.charAt(0).toUpperCase() + n.slice(1);
}
function stripS(n: string) {
return n.endsWith('s') ? n.substring(0, n.length - 1) : n;
}
}
printInferredTypes([fileName], {
noEmitOnError: true, noImplicitAny: true,
target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS
});
找到一个 npm 包,可以将没有模式的任意 JSON 文件转换为 TS 接口:
https://www.npmjs.com/package/json-to-ts
作者还提供了一个VSCode插件
仅使用 sed
和 tsc
sed '1s@^@const foo = @' sample.json > sample.$$.ts
tsc sample.$$.ts --emitDeclarationOnly --declaration
- 将
const foo =
追加到文件开头
使用 sed
将第一行 (1
) 开头的 (s
) 无任何内容 (@^@
) 替换为 const foo =
- 输出到
sample.$$.ts
扩展名必须是 .ts
$$
扩展为 shell 进程 ID,对于不太可能覆盖您关心的内容的临时文件很方便
- 要求
tsc
只发出一个 .d.ts
打字文件
这个文件几乎包含了你想要的界面的所有内容。您可能需要替换一些字符串并以您想要的方式对其进行自定义,但大部分腿部工作已完成
给定 api 的 JSON 输出:
{
"id": 13,
"name": "horst",
"cars": [{
"brand": "VW",
"maxSpeed": 120,
"isWastingGazoline": true
}]
}
我想为打字稿定义接口:
export interface Car {
brand: string;
maxSpeed: number;
isWastingGazoline: boolean;
}
export interface RaceCarDriver {
id: number;
name: string;
cars: Car[];
}
但我不希望他们手动输入它们,我宁愿让脚本为我生成它们。
这样的网络服务您可以使用 npm 模块代替网络托管解决方案:
https://www.npmjs.com/package/json-schema-to-typescript
如果你的 JSON 来自 HTTP API 并且 API 有一个 swagger 代码定义,你可以生成一个 TypeScript 客户端:
https://github.com/swagger-api/swagger-codegen#api-clients
如果您 json 来自 Java ot .Net 支持,您可以从 Java 或 C# 类:
生成 TypeScript您可以使用 typescript compiler API 及其推断类型的能力编写脚本。我真的很惊讶它是多么容易。
您必须包装您的示例数据以使其可编译为打字稿代码。该脚本将选择所有变量声明并尝试为它们打印推断类型。它使用变量名和 属性 名称来为类型分配名称,如果两个对象具有相同名称的 属性,它将从第一个对象中选择类型。因此,如果这些类型实际上不同,它将不起作用(修复留作练习)。对于您的 JSON 输出,数据样本将类似于
文件sample.ts
let raceCarDriver = {
"id": 13,
"name": "horst",
"cars": [{
"brand": "VW",
"maxSpeed": 120,
"isWastingGazoline": true,
}]
};
脚本已使用 Typescript 2.1(刚刚发布)进行测试:
npm i typescript
npm i @types/node
./node_modules/.bin/tsc --lib es6 print-inferred-types.ts
node print-inferred-types.js sample.ts
输出:
export interface RaceCarDriver {
id: number;
name: string;
cars: Car[];
}
export interface Car {
brand: string;
maxSpeed: number;
isWastingGazoline: boolean;
}
这是脚本:print-inferred-types.ts
:
import * as ts from "typescript";
let fileName = process.argv[2];
function printInferredTypes(fileNames: string[], options: ts.CompilerOptions): void {
let program = ts.createProgram(fileNames, options);
let checker = program.getTypeChecker();
let knownTypes: {[name: string]: boolean} = {};
let pendingTypes: {name: string, symbol: ts.Symbol}[] = [];
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile.fileName == fileName) {
ts.forEachChild(sourceFile, visit);
}
}
while (pendingTypes.length > 0) {
let pendingType = pendingTypes.shift();
printJsonType(pendingType.name, pendingType.symbol);
}
function visit(node: ts.Node) {
if (node.kind == ts.SyntaxKind.VariableStatement) {
(<ts.VariableStatement>node).declarationList.declarations.forEach(declaration => {
if (declaration.name.kind == ts.SyntaxKind.Identifier) {
let identifier = <ts.Identifier>declaration.name;
let symbol = checker.getSymbolAtLocation(identifier);
if (symbol) {
let t = checker.getTypeOfSymbolAtLocation(symbol, identifier);
if (t && t.symbol) {
pendingTypes.push({name: identifier.text, symbol: t.symbol});
}
}
}
});
}
}
function printJsonType(name: string, symbol: ts.Symbol) {
if (symbol.members) {
console.log(`export interface ${capitalize(name)} {`);
Object.keys(symbol.members).forEach(k => {
let member = symbol.members[k];
let typeName = null;
if (member.declarations[0]) {
let memberType = checker.getTypeOfSymbolAtLocation(member, member.declarations[0]);
if (memberType) {
typeName = getMemberTypeName(k, memberType);
}
}
if (!typeName) {
console.log(`// Sorry, could not get type name for ${k}!`);
} else {
console.log(` ${k}: ${typeName};`);
}
});
console.log(`}`);
}
}
function getMemberTypeName(memberName: string, memberType: ts.Type): string | null {
if (memberType.flags == ts.TypeFlags.String) {
return 'string';
} else if (memberType.flags == ts.TypeFlags.Number) {
return 'number';
} else if (0 !== (memberType.flags & ts.TypeFlags.Boolean)) {
return 'boolean';
} else if (memberType.symbol) {
if (memberType.symbol.name == 'Array' && (<ts.TypeReference>memberType).typeArguments) {
let elementType = (<ts.TypeReference>memberType).typeArguments[0];
if (elementType && elementType.symbol) {
let elementTypeName = capitalize(stripS(memberName));
if (!knownTypes[elementTypeName]) {
knownTypes[elementTypeName] = true;
pendingTypes.push({name: elementTypeName, symbol: elementType.symbol});
}
return `${elementTypeName}[]`;
}
} else if (memberType.symbol.name == '__object') {
let typeName = capitalize(memberName);
if (!knownTypes[typeName]) {
knownTypes[typeName] = true;
pendingTypes.push({name: typeName, symbol: memberType.symbol});
}
return typeName;
} else {
return null;
}
} else {
return null;
}
}
function capitalize(n: string) {
return n.charAt(0).toUpperCase() + n.slice(1);
}
function stripS(n: string) {
return n.endsWith('s') ? n.substring(0, n.length - 1) : n;
}
}
printInferredTypes([fileName], {
noEmitOnError: true, noImplicitAny: true,
target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS
});
找到一个 npm 包,可以将没有模式的任意 JSON 文件转换为 TS 接口: https://www.npmjs.com/package/json-to-ts
作者还提供了一个VSCode插件
仅使用 sed
和 tsc
sed '1s@^@const foo = @' sample.json > sample.$$.ts
tsc sample.$$.ts --emitDeclarationOnly --declaration
- 将
const foo =
追加到文件开头
使用sed
将第一行 (1
) 开头的 (s
) 无任何内容 (@^@
) 替换为const foo =
- 输出到
sample.$$.ts
扩展名必须是.ts
$$
扩展为 shell 进程 ID,对于不太可能覆盖您关心的内容的临时文件很方便 - 要求
tsc
只发出一个.d.ts
打字文件
这个文件几乎包含了你想要的界面的所有内容。您可能需要替换一些字符串并以您想要的方式对其进行自定义,但大部分腿部工作已完成