← All guides

Protobuf Text Format vs Binary Wire Format — Why One Converts to JSON and the Other Can't

Protocol Buffers has two completely different serializations, and mixing them up is the single most common confusion around this format. The binary wire format is what actually goes over the network — compact, fast, and built around field numbers, not names. A compiled .proto schema is required to make sense of it, because the bytes alone don't say whether field 3 is called user_id or account. Text format is the other one: a human-readable, self-describing representation used in config files and debug logs, where field names appear directly as text. That's the one this tool converts — a browser tool with no schema input has no way to interpret field numbers, but field names in a pasted document need no schema at all.

Repeated fields merge by appearing more than once

Text format has no array syntax at the field level — a repeated field is just the same key written multiple times. The parser here starts a key as a plain scalar the first time it's seen, and only converts it into an array the moment the same key shows up a second time:

tags: "urgent"
tags: "billing"
tags: "customer-a"

↓

{ "tags": ["urgent", "billing", "customer-a"] }

A single occurrence of a field stays a plain scalar in the JSON output — it does not become a one-element array. If your downstream code expects tags to always be an array, even when there's only one tag, you need to normalize that yourself after conversion; the parser reflects exactly what appeared in the text, not a guess about the field's declared cardinality (which, again, isn't known without a schema).

Escape sequences: only the quote gets escaped, not backslash-n

Advertisement

This is a real limitation worth knowing before you paste a text-proto value copied from a debug log: backslash-escapes inside quoted strings are parsed as "skip the backslash, keep the next character literally." That correctly handles an escaped quote (\") inside a string, but it does not interpret the standard C-style escapes real protobuf text format supports — \n comes through as the two literal characters backslash and n, not a newline:

message: "line one\nline two"

↓ (what you might expect)
{ "message": "line one\nline two" }   // with an actual newline

↓ (what this parser actually produces)
{ "message": "line onenline two" }   // backslash dropped, "n" kept literally

If a field value round-trips through this tool with an unexpected letter where a newline or tab should be, this is why. Values with only literal characters and escaped quotes convert exactly as expected — it's specifically \n, \t, and similar control-character escapes that don't decode.

Numbers, booleans, and bare identifiers are guessed from the raw text

An unquoted value is checked against true/false first, then run through Number()enabled: true becomes a JSON boolean, retries: 3 becomes a JSON number, and anything that doesn't parse as either stays a string. There's no schema telling the parser that a particular field is actually an enum name that happens to look like a word — it's a best-effort guess based purely on what the text looks like, the same constraint that makes the whole schema-less conversion possible in the first place.

Advertisement

Try the Protobuf Text ↔ JSON