100% local — your data never leaves your browser

JSON to Protobuf — A .proto protoc Compiles

Turn a JSON sample into proto3 messages. Field names are repaired for protobuf and json_name keeps the original key, so both sides still agree.

Instant Private Zero cookies

JSON input

Protobuf output

What this tool does

A JSON sample becomes proto3 messages, one per object.

{ "id": 1, "name": "Ada", "tags": ["admin"], "address": { "city": "Paris" } }
syntax = "proto3";

message Root {
  int64 id = 1;
  string name = 2;
  repeated string tags = 3;
  Address address = 4;
}

message Address {
  string city = 1;
}

Field numbers start at 1 in each message, in the order the keys appeared. They are what protobuf puts on the wire: names are for humans, numbers are the contract.

The original key is kept

A protobuf field name is [a-z_][a-z0-9_]* by convention and cannot contain a dash. So content-type becomes content_type — and here is the part that bites: proto3’s JSON mapping derives the JSON key from the field name, in lowerCamelCase. content_type maps to contentType, which is not the key your data has.

string content_type = 1 [json_name = "content-type"];

json_name pins the original key, so the message still reads the document it was generated from. The option is emitted only when the repair changed something — a key already in snake_case needs nothing, because a proto3 parser accepts the field name as written too.

This was a real defect: the generated .proto silently stopped matching its own input. It was found while writing this page and fixed.

64-bit integers travel as strings

Integers become int64. In proto3’s JSON mapping, int64 is encoded as a string:

{ "id": "12345678901" }

That is the specification, not a choice made here — JSON numbers cannot carry 64 bits without risk, so the mapping sidesteps them. It does mean a round trip through protobuf changes the type your consumer sees. If the field is a small counter, int32 is an edit worth making.

What proto3 cannot say, it says with Value

  • null and unknown values become google.protobuf.Value, and import "google/protobuf/struct.proto" is added — only when something needs it.
  • A nested array becomes google.protobuf.ListValue: proto3 forbids repeated repeated, so an array of arrays has no direct shape.
  • A heterogeneous array becomes repeated google.protobuf.Value, except when it mixes integers and floats, where repeated double covers both.

Presence is not what you might expect

In proto3, a scalar field with no optional keyword has no presence: a missing field and a field set to 0, "" or false are indistinguishable after decoding. Nothing in a JSON sample says which keys are truly optional, so nothing here is marked optional. Where the difference matters — a nullable score, a flag that may be unset — adding the keyword is the edit to make.

Private by design

Everything runs locally in your browser with JavaScript. Your data is never uploaded, which makes the tool safe for sensitive content, and it keeps working offline.

Frequently asked questions

Why do some fields carry `[json_name = "…"]`?
Because their name had to change. A protobuf field name cannot hold a dash, so `content-type` becomes `content_type` — and proto3\u2019s JSON mapping then looks for `contentType`, not for the key you actually have. `json_name` pins the original. Without it the message no longer reads the JSON it was generated from; that was a real defect, fixed.
Why are my 64-bit integers strings in the JSON output?
That is proto3\u2019s JSON mapping, not this tool: `int64`, `uint64` and `fixed64` are encoded as strings, because JSON numbers cannot carry 64 bits safely. Your `{"id": 12345678901}` becomes `{"id": "12345678901"}` after a round trip through protobuf. Worth knowing before a client on the other side chokes on it.
Can I edit the field numbers?
You can, but renumbering an existing message breaks compatibility with data already encoded — the number, not the name, is what goes on the wire. The numbers here start at 1 per message, which is right for a new definition. For a message already in production, keep the existing numbers and give new fields unused ones.

Related converters