Protobuf to JSON Schema 변환기
.proto 스키마를 붙여넣으세요. 각 message와 enum마다 하나의 $defs 항목을 가진 JSON Schema 2020-12 문서를 받게 되며, Ajv나 모든 모던 검증기에서 바로 사용할 수 있습니다.
입력 (.proto 스키마)
출력 (JSON Schema)
이 도구가 하는 일
Protocol Buffers 스키마가 있고, JSON을 받는 서비스가 있다고 합시다 — webhook 핸들러일 수도, HTTP transcoding을 하는 gRPC 게이트웨이일 수도, 요청이 코드에 닿기 전에 검증을 수행하는 API 게이트웨이일 수도 있습니다. proto를 거울처럼 반영하는 JSON Schema 문서가 필요합니다 — 들어오는 페이로드를 검증하거나, OpenAPI 조각을 만들거나, 구조화된 출력 프롬프트에 넣기 위해서요. 이 변환기는 그것을 브라우저 안에서 처리합니다 — .proto를 붙여넣고 JSON Schema를 복사해 검증기 설정에 떨어뜨리세요.
출력은 JSON Schema draft 2020-12입니다 — 현재 draft이며 Ajv를 포함한 모든 모던 검증기가 지원합니다. 각 message는 $defs 아래의 항목이 되어 type: "object"와 properties 맵을 가집니다. 각 enum은 $defs 항목이 되어 type: "string"과 enum 아래에 값 이름이 나열됩니다 — proto3가 JSON에서 enum을 이름으로 직렬화하는 방식과 일치합니다. 필드 참조는 leaf 이름을 사용한 $ref: "#/$defs/MessageName"로 해결되어 중첩 타입이 읽기 좋은 상태로 유지됩니다.
타입 매핑은 proto3 JSON 매핑 사양을 따릅니다. string/bool은 그대로 매핑됩니다. 32비트 int는 type: "integer"와 format: "int32"가 되고, unsigned 32비트는 minimum: 0이 추가됩니다. 64비트 int는 type: "string"과 format: "int64" 그리고 숫자 패턴이 됩니다. 이유는 JSON Number가 2^53 위에서 정밀도를 잃고, proto3는 64비트 정수를 wire 상에서 따옴표로 묶인 문자열로 인코딩하기 때문입니다. bytes는 contentEncoding: "base64"가 붙은 string이 됩니다. google.protobuf.Timestamp 같은 well-known 타입은 RFC 3339에 따라 format: "date-time"으로 매핑됩니다. 변환은 전적으로 브라우저 내에서 실행됩니다 — 스키마는 페이지를 떠나지 않습니다.
사용법
세 단계. 출력은 검증기에 그대로 넘길 수 있는 단일 JSON Schema 문서입니다.
.proto 스키마를 붙여넣으세요
왼쪽 에디터에 스키마를 떨어뜨리세요. 맨 위의 syntax = "proto3";는 있어도 좋고 없어도 됩니다. 파서는 중첩된 message 블록, enum 선언, oneof, map<K, V>, 필드 옵션을 처리합니다. import 지시어는 인식하지만 건너뜁니다 — 필요하면 import 대상 타입을 인라인으로 붙여넣으세요.
필드 이름은 snake_case로 유지됩니다 (proto3 JSON 인코더가 기본으로 출력하는 것과 일치 — 변환 없음). 클라이언트가 preserve_proto_field_names = false를 설정한다면 프로퍼티 키를 수동으로 camelCase로 바꾸세요.
스키마 읽기
오른쪽에 보이는 것: $id, title, 마지막으로 선언된 루트 message를 가리키는 최상위 $ref, 파일의 모든 message와 enum을 담은 $defs 블록을 가진 JSON Schema 2020-12 문서. 각 message는 properties 맵을 가진 객체 스키마가 되고, 각 enum은 값 이름을 가진 string 스키마가 됩니다. $ref나 $defs 의미를 다시 보고 싶다면 Understanding JSON Schema 가이드를 읽어보세요.
검증기에 연결하기
출력을 schema.json으로 저장하고 Ajv(Node)나 jsonschema(Python) 또는 스택에서 사용하는 검증기에 로드한 다음, gRPC 게이트웨이나 webhook이 받는 JSON에 대해 실행하세요. 불일치는 /items/0/sku must be string 같은 읽기 좋은 에러 경로로 나옵니다. 같은 스키마는 OpenAPI 3.1 컴포넌트 정의나 LLM의 구조화된 출력 프롬프트에도 그대로 사용할 수 있습니다.
실제로 시간을 절약하는 순간
proto로 정의된 webhook 검증
팀에서 OrderShipped 이벤트의 진실의 원천으로 Protobuf를 쓰지만, 실제 webhook 수신측은 JSON을 받습니다 — 수신측에 proto 런타임이 없어요. .proto를 붙여넣고 JSON Schema를 Ajv에 떨어뜨리면 비즈니스 로직에 닿기 전 엣지에서 잘못된 페이로드를 거절할 수 있습니다. quantity가 빠진 SKU-101은 데이터베이스까지 가지 않습니다.
gRPC 스키마에서 OpenAPI 3.1 만들기
gRPC 게이트웨이용 OpenAPI 3.1 스펙을 작성하고 있어요. OpenAPI 3.1은 JSON Schema 2020-12 호환이라, 여기의 $defs 블록이 약간의 이름 변경 후 components.schemas 아래로 바로 떨어집니다. protoc-gen-openapi 플러그인 설치도, Buf CLI 세팅도 필요 없습니다 — 그냥 붙여넣고, 편집하고, 커밋하세요.
proto에서 LLM 구조화 출력
OpenAI나 Anthropic이 기존 .proto와 일치하는 타입화된 Order 객체를 반환하도록 만들고 싶습니다. 스키마를 붙여넣고 $defs/Order 항목을 가져와 response_format의 JSON Schema로 전달하세요. 이제 모델은 수동 변환 없이 gRPC 서비스를 왕복할 수 있는 출력을 만듭니다.
Protobuf API 변경 리뷰
백엔드 동료가 Address에 두 개의 필드를 추가하고 enum 값 하나의 이름을 바꿨습니다. 전체 codegen 파이프라인을 돌리지 않고 그것이 게이트웨이가 사용하는 JSON Schema에 어떤 영향을 미치는지 보고 싶습니다. 새 .proto를 붙여넣고 커밋된 사본과 스키마를 diff한 다음, PR에 초점이 맞은 리뷰 코멘트를 남기세요.
자주 묻는 질문
제 스키마가 어딘가로 전송되나요?
아니요. 파서와 JSON Schema 출력기는 모두 브라우저에서 JavaScript로 동작합니다. 붙여넣으면서 DevTools의 Network 탭을 보세요 — 요청은 0건입니다. 스키마에 내부 package 경로, 타입 이름, 외부 서비스에 넘기고 싶지 않은 무언가가 있을 때 유용합니다.
출력은 어떤 JSON Schema draft를 대상으로 하나요?
현재 발행 중인 Draft 2020-12입니다. 모든 출력 문서의 $schema URI는 https://json-schema.org/draft/2020-12/schema입니다. 2019-09 이후 변경 사항은 2020-12 릴리스 노트를 참고하세요. 적극적으로 유지보수되는 검증기(Ajv 8+, Python의 jsonschema 4+, NJsonSchema, Java의 Justify)는 모두 기본으로 2020-12를 지원합니다.
왜 int64 필드가 integer가 아니라 string인가요?
proto3 JSON 매핑 사양이 그렇게 말하기 때문이고, 그 결정이 옳기 때문입니다. JSON Number는 IEEE-754 double이라 2^53을 넘으면 정밀도를 잃습니다. 진짜 int64는 그 한도를 훨씬 넘는 값을 운반할 수 있어요 — 주문 ID, 나노초 단위 타임스탬프, 원장 잔액 — 그래서 proto3는 64비트 정수를 따옴표로 묶인 JSON 문자열로 인코딩합니다. 스키마는 type: "string", format: "int64", 숫자 패턴으로 이를 반영하여 검증기가 여전히 "abc"를 거절할 수 있게 합니다. 만약 서버가 64비트 int를 raw JSON Number로 내보낸다면(일부 레거시 게이트웨이가 그렇게 합니다) 해당 항목을 수동으로 { "type": "integer" }로 바꾸세요.
왜 enum이 integer가 아니라 string인가요?
같은 이유 — proto3의 JSON 인코딩 기본값이기 때문입니다. enum은 wire 번호 정수(2)가 아니라 값 이름("ORDER_STATUS_PAID")으로 직렬화됩니다. 이로 인해 JSON 페이로드가 읽기 좋아지고 스키마가 단순해집니다. 정수 번호는 wire 포맷 관심사이지 검증 관심사가 아니기 때문에 JSON Schema에는 포함되지 않습니다. int를 출력하도록 구성된 비기본 인코더가 있다면 enum 항목의 type: "string"을 type: "integer"로 바꾸세요.
map<K, V>는 어떻게 처리하나요?
{ "type": "object", "additionalProperties": <V-schema> }로 렌더링됩니다. JSON 객체 키는 항상 문자열이므로, 비문자열 키를 가진 proto map(예: map<int32, string>)에는 런타임 키가 문자열로 강제 변환된다는 설명 노트가 붙습니다. 값 스키마는 일반 필드와 같은 타입 매핑 규칙을 따릅니다.
필드가 required로 표시되나요?
아니요 — proto3 필드는 wire 포맷에서 항상 기본값을 가지며 JSON 출력에도 항상 존재합니다("", 0, false, [], {} 같은 빈 기본값). 그래서 스키마는 required 아래에 아무것도 나열하지 않습니다. 검증 시점에 필드를 정말 required로 만들고 싶다면 부모 message의 required 배열에 수동으로 추가하세요. proto3의 optional이나 oneof도 출력에서 oneOf로 강제되지 않습니다 — 그것들은 추가 어노테이션 없이는 JSON Schema가 완전히 표현할 수 없는 런타임 의미입니다.
중첩된 message는 어떻게 참조되나요?
모든 message와 enum은 leaf 이름을 키로 하는 평평한 $defs 블록으로 끌어올려집니다. 필드 참조는 $ref: "#/$defs/MessageName"을 통해 갑니다. 평탄화는 문서를 컴팩트하게 유지하고 두 번 중첩된 타입이 중복되지 않게 합니다. 다른 package에 있는 두 message가 leaf 이름을 공유한다면, 변환기는 첫 번째 정의를 유지합니다 — 그게 중요하다면 붙여넣기 전에 충돌하는 이름을 분리하세요.
이걸 Ajv에 바로 꽂을 수 있나요?
네. Ajv가 draft 2020-12용으로 구성되어 있다면(ajv/dist/2020의 new Ajv2020()) 출력에 대한 ajv.compile(schema)가 그대로 동작합니다. 모든 게 같은 문서 안에 있으므로 $ref 항목은 내부적으로 해결됩니다. format 검증(date-time, duration)을 원하면 ajv-formats를 함께 추가하세요.
관련 도구
Protobuf, JSON Schema, 검증을 다루고 있다면 이 조합이 잘 어울립니다: