JSON to Protobuf / Avro Schema
Generate a proto3 message definition or an Avro record schema from any JSON object.
proto3 .proto message definitionMessage.proto
Paste a real JSON sample and get a starting proto3 message or Avro record schema back, instead of transcribing every field into a blank .proto or .avsc file by hand. Switch formats with one click — the same sample drives both outputs.
- ✓proto3 .proto message generation with sequential field numbers
- ✓Avro record schema (.avsc) generation with nullable unions
- ✓Nested objects become nested message/record types automatically
- ✓Arrays become repeated fields (Protobuf) or array types (Avro)
- ✓100% private — the schema is generated entirely in your browser
Why move from JSON to Protobuf or Avro?
JSON is the easiest format to prototype with — human-readable, no schema compiler, works everywhere. Protobuf and Avro trade that ease for a compact binary wire format and a schema every producer and consumer agrees on ahead of time, which is why they show up once a system needs smaller payloads, faster serialization, or safe schema evolution across services. See JSON vs. Protobuffor the full tradeoff comparison — this tool is the practical next step once you've decided to make the move: paste a real JSON payload and get a starting schema instead of hand-writing one field by field.
Protobuf vs. Avro — Which Should You Generate?
| Protobuf | Avro | |
|---|---|---|
| Schema format | .proto IDL text, compiled to code | JSON schema (.avsc), often used directly |
| Needs a code generator | Yes — protoc or buf | Optional — many Avro libraries read .avsc at runtime |
| Common ecosystem | gRPC services, microservices | Kafka, Hadoop, data pipelines |
| Schema evolution model | Numbered fields — add, don't renumber | Reader/writer schema resolution |
| Self-describing on disk | No — needs the .proto to decode | Yes — schema can travel with the data |
Using a Generated .proto File
syntax = "proto3";
package jsonkit;
message Order {
string order_id = 1;
int32 user_id = 2;
double total = 3;
bool paid = 4;
repeated Items items = 5;
}Compile it with protoc (or buf generate) for your target language, then encode/decode against the generated types. Field numbers are assigned in the order fields appear in your sample — review them before shipping, since changing a field number later breaks wire compatibility with anything still using the old numbering.
Field Numbering Rules Worth Knowing
Protobuf field numbers aren't cosmetic — they're what actually gets written on the wire instead of the field name, so they need a few rules respected once a message is in real use:
- Numbers 1–15 take one byte to encode instead of two — assign them to your most frequently-set fields for a small but free size win.
- Numbers 19000–19999 are reserved by the Protobuf implementation itself and can't be used.
- Once a field number ships to production, it can never be reassigned to a different field — mark a removed field's number as
reservedinstead of reusing it, so an old binary reading new data (or vice versa) can't misinterpret a field. - Adding a new field with a new, unused number is always safe and is how Protobuf schemas evolve without breaking older readers or writers.
Using a Generated Avro Schema
Switch the tool to Avro mode and the same sample produces a record schema instead:
{
"type": "record",
"name": "Order",
"fields": [
{ "name": "order_id", "type": "string" },
{ "name": "user_id", "type": "long" },
{ "name": "total", "type": "double" },
{ "name": "paid", "type": "boolean" }
]
}Reading data written against it, in Python with fastavro:
# pip install fastavro
import fastavro
schema = fastavro.parse_schema(order_schema) # the JSON above, as a dict
with open("orders.avro", "rb") as f:
for record in fastavro.reader(f):
print(record["order_id"], record["total"])Because Avro's schema is itself JSON, it's directly diffable and reviewable in a normal pull request — a real practical advantage over Protobuf's compiled .proto during schema review.
A Note on Inferred Types
Types are inferred from a single JSON sample, so treat the output as a first draft. A field that was null in your sample becomes a placeholder string (Protobuf) or a nullable ["null", "string"] union (Avro) with a comment or default flagging it — replace it with the real type once you know it. Large integers are emitted as int64/long automatically when a sample value exceeds the 32-bit range.