免费在线TypeScript转JSON Schema

免费在线TypeScript转JSON Schema工具,将TypeScript类型定义转换为JSON Schema,支持接口/类型/枚举,无需注册。 | 无需注册 · 数据绝不上传服务器

零依赖·可离线使用
⚙️ 转换选项
📝 TypeScript类型定义
🔒所有转换在浏览器本地完成,数据绝不上传

数据会上传服务器吗?

不会。所有TypeScript解析和JSON Schema生成均在浏览器本地完成,数据绝不上传服务器。

支持联合类型吗?

支持。联合类型会转换为JSON Schema的oneOf或enum结构,如type Status = 'active' | 'inactive'转为enum数组。

能处理嵌套对象吗?

可以。嵌套对象会递归转换为JSON Schema的properties和required结构,支持多层嵌套。

生成的JSON Schema版本是什么?

默认生成JSON Schema Draft 2020-12版本,也可选择Draft 7版本。

能导出结果吗?

可以。支持复制JSON Schema到剪贴板和下载为JSON文件。

TypeScript转JSON Schema能做什么?

TypeScript转JSON Schema工具是一款纯前端在线工具,用于将TypeScript类型定义转换为JSON Schema。支持interface接口、type类型别名、enum枚举、基本类型、数组、嵌套对象、联合类型、可选属性和字面量类型。转换后的JSON Schema可用于API文档、表单验证、代码生成等场景。所有转换在浏览器本地完成,数据绝不上传服务器。

核心功能

使用教程

步骤1:选择JSON Schema版本(Draft 2020-12或Draft 7)。

步骤2:在文本框输入TypeScript类型定义,或点击"加载示例"。

步骤3:点击"转换Schema"或使用Ctrl+Enter,查看生成的JSON Schema。

步骤4:点击"复制Schema"复制到剪贴板,或"下载JSON"保存为文件。

应用场景

API文档

将TypeScript接口定义转换为JSON Schema,用于OpenAPI/Swagger文档生成,确保前后端类型一致。

表单验证

利用JSON Schema驱动前端表单验证(如ajv、jsonschema库),从TypeScript类型自动生成验证规则。

代码生成

从TypeScript类型生成JSON Schema,再用于生成其他语言的类型定义、Mock数据或文档。

配置验证

将配置文件的TypeScript类型转为JSON Schema,用于验证JSON/YAML配置文件的正确性。

扩展知识

JSON Schema是描述JSON数据结构的标准,当前最新版本为Draft 2020-12。TypeScript类型系统和JSON Schema有天然对应关系:string对应{"type":"string"},number对应{"type":"number"},interface对应{"type":"object","properties":{...}}。可选属性prop?: type不出现在required数组中。联合类型'a'|'b'转为{"enum":["a","b"]},string|number转为{"oneOf":[{"type":"string"},{"type":"number"}]}。数组Type[]转为{"type":"array","items":{...}}。

常见问题 (FAQ)

支持哪些TypeScript类型?

支持interface、type别名、enum枚举、基本类型(string/number/boolean)、数组、嵌套对象、联合类型、可选属性和字面量类型。

function showToast(m){var t=document.getElementById("toast");t.textContent=m;t.classList.add("show");setTimeout(function(){t.classList.remove("show")},3000)} function copyText(id){var el=document.getElementById(id);if(!el)return;var t=el.textContent||el.innerText;navigator.clipboard.writeText(t).then(function(){showToast("已复制")})["catch"](function(){showToast("复制失败")})} 'use strict'; const tsInput=document.getElementById('tsInput'); const schemaOutput=document.getElementById('schemaOutput'); const resultPanel=document.getElementById('resultPanel'); const statGrid=document.getElementById('statGrid'); const errorBox=document.getElementById('errorBox'); let schemaDraft='2020-12'; let useRootRef=false; function setDraft(d){ schemaDraft=d; document.getElementById('schemaDraft12').classList.toggle('active',d==='2020-12'); document.getElementById('schemaDraft7').classList.toggle('active',d==='7'); if(tsInput.value.trim())convert(); } window.setDraft=setDraft; function toggleRootRef(){ useRootRef=!useRootRef; document.getElementById('optRoot').classList.toggle('active',useRootRef); if(tsInput.value.trim())convert(); } window.toggleRootRef=toggleRootRef; function parseTS(text){ const types={}; const lines=text.split('\n'); let currentName=null;let currentBody='';let braceCount=0;let inBlock=false;let blockType=''; for(const line of lines){ const trimmed=line.trim(); if(!trimmed||trimmed.startsWith('//')||trimmed.startsWith('/*'))continue; const ifaceMatch=trimmed.match(/^interface\s+(\w+)\s*(?:extends\s+\w+\s*)?\{/); const typeMatch=trimmed.match(/^type\s+(\w+)\s*=\s*/); const enumMatch=trimmed.match(/^enum\s+(\w+)\s*\{/); if(ifaceMatch&&!inBlock){currentName=ifaceMatch[1];blockType='interface';inBlock=true;braceCount=1;currentBody='';continue;} if(enumMatch&&!inBlock){currentName=enumMatch[1];blockType='enum';inBlock=true;braceCount=1;currentBody='';continue;} if(typeMatch&&!inBlock){ currentName=typeMatch[1];blockType='type'; const rest=trimmed.substring(typeMatch[0].length); if(rest.includes(';')||rest.endsWith(';')){ const val=rest.replace(/;$/,'').trim(); types[currentName]=parseTypeValue(val); currentName=null;blockType='';continue; } if(rest.includes('{')){braceCount=(rest.match(/{/g)||[]).length-(rest.match(/}/g)||[]).length;inBlock=true;currentBody=rest;continue;} currentBody=rest;inBlock=true;continue; } if(inBlock){ braceCount+=(line.match(/{/g)||[]).length-(line.match(/}/g)||[]).length; currentBody+='\n'+trimmed; if(braceCount<=0){ if(blockType==='enum')types[currentName]=parseEnum(currentBody); else if(blockType==='interface')types[currentName]=parseInterface(currentBody); else types[currentName]=parseTypeValue(currentBody.replace(/;$/,'').trim()); inBlock=false;currentName=null;blockType=''; } } } return types; } function parseEnum(body){ const members=body.replace(/[{}]/g,'').split(',').map(m=>m.trim().split('=')[0].trim().replace(/^['"]|['"]$/g,'')).filter(Boolean); const isNumeric=members.every(m=>/^\d+$/.test(m)); return{type:isNumeric?'number':'string',enum:isNumeric?members.map(Number):members}; } function parseInterface(body){ const props={};const required=[]; const lines=body.replace(/[{}]/g,'').split('\n'); for(const line of lines){ const trimmed=line.trim().replace(/;$/,'').replace(/,$/,''); if(!trimmed||trimmed.startsWith('//'))continue; const propMatch=trimmed.match(/^(\w+)(\??):\s*(.+)$/); if(propMatch){ const name=propMatch[1];const optional=propMatch[2]==='?';const typeStr=propMatch[3].trim(); if(!optional)required.push(name); props[name]=parseTypeValue(typeStr); } } return{type:'object',properties:props,required:required.length?required:undefined}; } function parseTypeValue(str){ str=str.trim(); if(str==='string')return{type:'string'}; if(str==='number')return{type:'number'}; if(str==='boolean')return{type:'boolean'}; if(str==='null')return{type:'null'}; if(str==='any'||str==='unknown')return{}; if(str==='undefined')return{type:'null'}; if(str.startsWith("'")||str.startsWith('"')){ const literals=[]; const regex=/'([^']*)'|"([^"]*)"/g;let m; while((m=regex.exec(str))!==null)literals.push(m[1]||m[2]); if(literals.length>1)return{enum:literals}; if(literals.length===1)return{const:literals[0]}; } if(/^\d+$/.test(str))return{const:parseInt(str)}; if(str.endsWith('[]')){ const itemType=parseTypeValue(str.slice(0,-2)); return{type:'array',items:itemType}; } if(str.startsWith('Array<')){ const itemType=parseTypeValue(str.slice(6,-1)); return{type:'array',items:itemType}; } if(str.includes('|')){ const parts=str.split('|').map(p=>p.trim()); const allLiterals=parts.every(p=>/^['"]/.test(p)); if(allLiterals){ const literals=parts.map(p=>p.replace(/^['"]|['"]$/g,'')); return{enum:literals}; } return{oneOf:parts.map(p=>parseTypeValue(p))}; } if(str.includes('&')){ const parts=str.split('&').map(p=>p.trim()); return{allOf:parts.map(p=>parseTypeValue(p))}; } if(str.startsWith('{')&&str.endsWith('}')){ return parseInterface(str); } return{$ref:'#/$defs/'+str}; } function convert(){ const text=tsInput.value.trim(); if(!text){resultPanel.style.display='none';return;} errorBox.innerHTML=''; try{ const types=parseTS(text); const typeNames=Object.keys(types); if(!typeNames.length){errorBox.innerHTML='\x3Cdiv style="color:#fca5a5;font-size:.85rem;margin-top:8px">未检测到有效的TypeScript类型定义\x3C/div>';return;} const firstType=typeNames[0]; const draftUri=schemaDraft==='7'?'http://json-schema.org/draft-07/schema#':'https://json-schema.org/draft/2020-12/schema'; const schema={ $schema:draftUri, title:firstType, ...(useRootRef?{$ref:'#/$defs/'+firstType}:{...types[firstType]}) }; if(typeNames.length>1||useRootRef){ schema.$defs={}; typeNames.forEach(n=>{schema.$defs[n]=types[n];}); } // Resolve $ref in properties function resolveRefs(obj){ if(!obj||typeof obj!=='object')return obj; if(Array.isArray(obj))return obj.map(resolveRefs); const result={}; for(const[k,v]of Object.entries(obj)){ if(k==='$ref'&&typeof v==='string'&&v.startsWith('#/$defs/')){ const refName=v.replace('#/$defs/',''); if(types[refName])result[k]=v;else result[k]=v; }else{ result[k]=resolveRefs(v); } } return result; } const resolved=resolveRefs(schema); const output=JSON.stringify(resolved,null,2); schemaOutput.value=output; resultPanel.style.display='block'; const propCount=types[firstType]&&types[firstType].properties?Object.keys(types[firstType].properties).length:0; statGrid.innerHTML=` \x3Cdiv class="stat-card">\x3Cdiv class="stat-value">${typeNames.length}\x3C/div>\x3Cdiv class="stat-label">类型数量\x3C/div>\x3C/div> \x3Cdiv class="stat-card">\x3Cdiv class="stat-value">${propCount}\x3C/div>\x3Cdiv class="stat-label">属性数量\x3C/div>\x3C/div> \x3Cdiv class="stat-card">\x3Cdiv class="stat-value" style="color:#10b981">Draft ${schemaDraft}\x3C/div>\x3Cdiv class="stat-label">Schema版本\x3C/div>\x3C/div>`; }catch(e){ errorBox.innerHTML=`\x3Cdiv style="color:#fca5a5;font-size:.85rem;margin-top:8px">解析错误:${e.message}\x3C/div>`; } } document.getElementById('btnConvert').addEventListener('click',convert); document.getElementById('btnClear').addEventListener('click',()=>{tsInput.value='';schemaOutput.value='';resultPanel.style.display='none';errorBox.innerHTML='';}); document.getElementById('btnCopy').addEventListener('click',()=>{ navigator.clipboard.writeText(schemaOutput.value).then(()=>{const b=document.getElementById('btnCopy');b.textContent='✅ 已复制';setTimeout(()=>b.textContent='📋 复制Schema',1500);}); }); document.getElementById('btnDownload').addEventListener('click',()=>{ const b=new Blob([schemaOutput.value],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download='schema.json';a.click(); }); document.addEventListener('keydown',e=>{if(e.ctrlKey&&e.key==='Enter'){convert();e.preventDefault();}if(e.ctrlKey&&e.shiftKey&&e.key==='C'){navigator.clipboard.writeText(schemaOutput.value);e.preventDefault();}}); tsInput.addEventListener('input',()=>{if(tsInput.value.trim())convert();}); document.getElementById('btnLoadExample').addEventListener('click',()=>{ tsInput.value=`interface User { id: number; name: string; email?: string; age: number; role: 'admin' | 'editor' | 'viewer'; tags: string[]; address: Address; } interface Address { street: string; city: string; zipCode: string; country?: string; } type Status = 'active' | 'inactive' | 'suspended'; enum Priority { Low = 'low', Medium = 'medium', High = 'high' }`; convert(); });