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
nulland unknown values becomegoogle.protobuf.Value, andimport "google/protobuf/struct.proto"is added — only when something needs it.- A nested array becomes
google.protobuf.ListValue: proto3 forbidsrepeated 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, whererepeated doublecovers 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.