When multiple fields are sent together, Protobuf just concatenates them into one binary stream. The decoder doesn't need separators between fields. Each field tells the decoder how many bytes to consume before it moves on.
- 01
Read the tag
The first varint in each field is the tag. Its lower three bits identify the wire type, and the remaining bits identify the field number.
- 02
Read the payload
The wire type tells the decoder where the payload ends. A varint ends at the first byte with its top bit clear, so the end is found while reading it. A fixed field is 4 or 8 bytes. A length-delimited field carries its size in a prefix.
- 03
Consume that field
The decoder takes exactly those bytes, maps them to the schema field when it recognizes the number, and moves to the next field.
- 04
Repeat to the end
The stream carries no outer field count. Parsing picks up at the next byte and continues until the end of the message.
When a field number appears twice
A payload can contain the same field number more than once. For a singular scalar or string field, the last value wins. For a singular message field, the decoder merges each occurrence into the same message. Repeated fields append. Because of this, two serialized messages of the same type concatenated together parse as one message with both merged.
It also means serialization isn't canonical. The same message can be encoded as different bytes depending on field order, map order, unknown fields, and the library that wrote it, and deterministic serialization only promises the same output from the same binary. Don't compare or hash encoded messages to check whether they are equal. The Protobuf documentation explains the details.
Next
Binary Explorer
An interactive Protobuf binary explorer: edit a schema and message, then inspect the encoded bytes with every tag, length, and value annotated.