← ToolBox
/ Protobuf 解析器
🌙
༐ Protobuf 解析器
解析 .proto 文件(proto2/proto3)为结构化 JSON:syntax、package、message、enum、service、rpc。
解析
美化
校验
复制
清空
示例
输出结果(JSON)
等待输入...
已复制
const SAMPLE = 'syntax = "proto3";\npackage com.example;\n\nmessage User {\n int64 id = 1;\n string name = 2;\n repeated string tags = 3;\n message Address {\n string city = 1;\n string zip = 2;\n }\n Address address = 4;\n oneof contact {\n string email = 5;\n string phone = 6;\n }\n}\n\nenum Status {\n UNKNOWN = 0;\n ACTIVE = 1;\n INACTIVE = 2;\n}\n\nservice UserService {\n rpc GetUser(GetUserRequest) returns (User);\n rpc ListUsers(ListUsersRequest) returns (stream User);\n}'; function tokenizeProto(src){ const tokens = []; let i = 0; const isWs = c => /\s/.test(c); const isName = c => /[A-Za-z0-9_]/.test(c); while(i < src.length){ const c = src[i]; if(isWs(c)){ i++; continue; } if(c === '/' && src[i+1] === '/'){ while(i
.'.includes(c)){ tokens.push({t:'punct',v:c}); i++; continue; } tokens.push({t:'punct',v:c}); i++; } return tokens; } function parseProto(){ setErr(''); try{ const tokens = tokenizeProto(document.getElementById('input').value); let i = 0; const result = {syntax:'', package:'', imports:[], options:[], messages:[], enums:[], services:[]}; const peek = () => tokens[i]; const next = () => tokens[i++]; const expect = (v) => { if(!peek() || peek().v !== v) throw new Error('期望 '+v); return next(); }; const parseField = () => { const mods = []; while(peek() && ['repeated','optional','required'].includes(peek().v)) mods.push(next().v); let type = next().v; while(peek() && peek().v === '.'){ next(); type += '.' + next().v; } const name = next().v; let num = null; if(peek() && peek().v === '='){ next(); num = parseInt(next().v); } const options = {}; if(peek() && peek().v === '['){ next(); while(peek() && peek().v !== ']'){ const k = next().v; expect('='); const v = next().v; options[k] = v; if(peek() && peek().v === ',') next(); } expect(']'); } expect(';'); return {label:'', type, name, number:num, repeated:mods.includes('repeated'), optional:mods.includes('optional'), required:mods.includes('required'), options}; }; const parseOneof = () => { next(); const name = next().v; expect('{'); const fields = []; while(peek() && peek().v !== '}'){ const type = next().v; const fname = next().v; let num = null; if(peek() && peek().v === '='){ next(); num = parseInt(next().v); } expect(';'); fields.push({type, name:fname, number:num}); } expect('}'); return {kind:'oneof', name, fields}; }; const parseMessage = (depth) => { next(); const name = next().v; expect('{'); const msg = {name, fields:[], messages:[], enums:[], reserved:[], oneofs:[]}; while(peek() && peek().v !== '}'){ const t = peek().v; if(t === 'message') msg.messages.push(parseMessage(depth+1)); else if(t === 'enum') msg.enums.push(parseEnum()); else if(t === 'oneof') msg.oneofs.push(parseOneof()); else if(t === 'reserved'){ next(); const nums=[]; while(peek() && peek().v !== ';'){ nums.push(next().v); if(peek() && peek().v === ',') next(); } expect(';'); msg.reserved.push(nums.join(',')); } else if(t === 'option'){ next(); const k=next().v; expect('='); const v=next().v; expect(';'); msg.options=msg.options||{}; msg.options[k]=v; } else msg.fields.push(parseField()); } expect('}'); return msg; }; const parseEnum = () => { next(); const name = next().v; expect('{'); const values = []; while(peek() && peek().v !== '}'){ if(peek().v === 'option'){ next(); while(peek() && peek().v !== ';') next(); next(); continue; } const n = next().v; expect('='); const v = parseInt(next().v); expect(';'); values.push({name:n, value:v}); } expect('}'); return {name, values}; }; const parseService = () => { next(); const name = next().v; expect('{'); const rpcs = []; while(peek() && peek().v !== '}'){ if(peek().v !== 'rpc') { next(); continue; } next(); const rpcName = next().v; expect('('); const streamReq = peek().v === 'stream' ? (next(), true) : false; const reqType = next().v; expect(')'); if(peek().v !== 'returns') throw new Error('期望 returns'); next(); expect('('); const streamResp = peek().v === 'stream' ? (next(), true) : false; const respType = next().v; expect(')'); if(peek() && peek().v === '{'){ while(peek() && peek().v !== '}') next(); expect('}'); } else expect(';'); rpcs.push({name:rpcName, requestType:reqType, responseType:respType, requestStream:streamReq, responseStream:streamResp}); } expect('}'); return {name, rpcs}; }; while(i < tokens.length){ const t = peek().v; if(t === 'syntax'){ next(); expect('='); result.syntax = next().v; expect(';'); } else if(t === 'package'){ next(); let p = next().v; while(peek() && peek().v === '.'){ next(); p += '.' + next().v; } expect(';'); result.package = p; } else if(t === 'import'){ next(); const mode = (peek().v === 'public' || peek().v === 'weak') ? next().v : ''; result.imports.push({mode, path: next().v}); expect(';'); } else if(t === 'option'){ next(); const k = next().v; expect('='); const v = next().v; expect(';'); result.options.push({key:k, value:v}); } else if(t === 'message') result.messages.push(parseMessage(0)); else if(t === 'enum') result.enums.push(parseEnum()); else if(t === 'service') result.services.push(parseService()); else { next(); } } setOut(JSON.stringify(result, null, 2)); setErr('✅ 解析成功'); }catch(e){ setErr('❌ ' + e.message); } } function doBeautify(){ parseProto(); } function doMinify(){ setErr(''); try{ const tokens = tokenizeProto(document.getElementById('input').value); setOut(tokens.map(t=>t.v).join(' ')); setErr('✅ 压缩成功'); }catch(e){ setErr('❌ ' + e.message); } } function doValidate(){ setErr(''); try{ const tokens = tokenizeProto(document.getElementById('input').value); let depth = 0; for(const tk of tokens){ if(tk.v === '{' || tk.v === '[' || tk.v === '(') depth++; if(tk.v === '}' || tk.v === ']' || tk.v === ')') depth--; if(depth < 0) throw new Error('括号不匹配'); } if(depth !== 0) throw new Error('括号未闭合'); setOut('✅ .proto 语法正确\nTokens: ' + tokens.length + '\n括号平衡'); setErr('✅ 校验通过'); }catch(e){ setErr('❌ ' + e.message); } } function getIndent(){const v=document.getElementById('indent').value;return v==='tab'?'\t':' '.repeat(parseInt(v));} function setOut(s){document.getElementById('output').textContent=s;} function setErr(s){const e=document.getElementById('err');e.textContent=s||'';e.className='err'+(s?'':' ok');} function clearAll(){document.getElementById('input').value='';setOut('等待输入...');setErr('');} function loadSample(){document.getElementById('input').value=SAMPLE;doBeautify();}