Reading — step 1 of 5
Read
Unknown Fields and Well-Known Types
Forward compatibility via unknown-field preservation
When a parser sees a field number not in its schema, it must NOT drop it. It records the raw bytes (tag + value) in an "unknown fields" set and re-emits them on the next serialize. That is the mechanism that makes forward compatibility actually work: an old binary can round-trip a new message without losing the fields it doesn't understand.
In Python and Java this is automatic. In Go it requires the protoreflect machinery; in older language stubs (some C++ lite runtimes, some embedded generators) unknown-field preservation is explicitly OFF for size reasons — a known production gotcha.
Default values vs missing fields (proto3)
In proto3, scalar fields with the default value (0, "", false,
b"") are NOT serialized. The receiver cannot distinguish "field was set
to 0" from "field was never set". That ambiguity is why:
oneofexists (gives explicit presence to bundled fields).optionalwas re-added in proto3.3+ (optional int32 score = 1;).- The well-known wrapper types (
google.protobuf.Int32Value, etc.) exist — they're message types, so presence is explicit via the LEN wire wrapper.
Well-known types
google.protobuf.* ships with a small set of canonical messages every
implementation supports:
| Type | Use |
|---|---|
Timestamp | seconds (int64) + nanos (int32) |
Duration | same fields, semantic = span |
Any | type_url + value (packed payload) |
Empty | zero-field marker (for RPCs with no body) |
Struct/Value/ListValue | dynamic JSON-like data |
FieldMask | list of dotted paths (partial-update selector) |
Int32Value, StringValue, ... | nullable scalar wrappers |
Use them instead of inventing your own. Code generators, JSON conversion, and SDK utilities all special-case these names.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…