입력 (.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 상에서 따옴표로 묶인 문자열로 인코딩하기 때문입니다. bytescontentEncoding: "base64"가 붙은 string이 됩니다. google.protobuf.Timestamp 같은 well-known 타입은 RFC 3339에 따라 format: "date-time"으로 매핑됩니다. 변환은 전적으로 브라우저 내에서 실행됩니다 — 스키마는 페이지를 떠나지 않습니다.

사용법

세 단계. 출력은 검증기에 그대로 넘길 수 있는 단일 JSON Schema 문서입니다.

1

.proto 스키마를 붙여넣으세요

왼쪽 에디터에 스키마를 떨어뜨리세요. 맨 위의 syntax = "proto3";는 있어도 좋고 없어도 됩니다. 파서는 중첩된 message 블록, enum 선언, oneof, map<K, V>, 필드 옵션을 처리합니다. import 지시어는 인식하지만 건너뜁니다 — 필요하면 import 대상 타입을 인라인으로 붙여넣으세요.

필드 이름은 snake_case로 유지됩니다 (proto3 JSON 인코더가 기본으로 출력하는 것과 일치 — 변환 없음). 클라이언트가 preserve_proto_field_names = false를 설정한다면 프로퍼티 키를 수동으로 camelCase로 바꾸세요.

2

스키마 읽기

오른쪽에 보이는 것: $id, title, 마지막으로 선언된 루트 message를 가리키는 최상위 $ref, 파일의 모든 message와 enum을 담은 $defs 블록을 가진 JSON Schema 2020-12 문서. 각 message는 properties 맵을 가진 객체 스키마가 되고, 각 enum은 값 이름을 가진 string 스키마가 됩니다. $ref나 $defs 의미를 다시 보고 싶다면 Understanding JSON Schema 가이드를 읽어보세요.

3

검증기에 연결하기

출력을 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/2020new Ajv2020()) 출력에 대한 ajv.compile(schema)가 그대로 동작합니다. 모든 게 같은 문서 안에 있으므로 $ref 항목은 내부적으로 해결됩니다. format 검증(date-time, duration)을 원하면 ajv-formats를 함께 추가하세요.

관련 도구

Protobuf, JSON Schema, 검증을 다루고 있다면 이 조합이 잘 어울립니다: