Repeated, Maps & Well-Known
Repeated fields are lists
Section titled “Repeated fields are lists”Add repeated to a field and it becomes an ordered list of that type — zero or more values:
message Playlist { string name = 1; repeated string songs = 2; // 0..N strings repeated Track tracks = 3; // 0..N messages}Repeated fields preserve order, and an empty list is just the default — you never send “null vs empty,” they’re the same. For numeric scalar lists, protobuf uses packed encoding by default in proto3, storing the values back-to-back without a tag per element, which keeps large numeric arrays compact.
Maps are key/value pairs
Section titled “Maps are key/value pairs”A map is syntactic sugar for a repeated key/value entry, giving you an associative container:
message Config { map<string, string> labels = 1; map<int64, User> user_by_id = 2;}Keys can be any integer, bool, or string type; values can be any type except another map. Two caveats worth knowing: map ordering is not guaranteed on the wire, and maps can’t be repeated (wrap them in a message if you need a list of maps).
Well-known types: protobuf’s standard library
Section titled “Well-known types: protobuf’s standard library”You don’t model time, duration, or “any value” from scratch. The google.protobuf package ships well-known types — import and use them.
flowchart LR time["Timestamp / Duration → points in time & spans"] wrap["Int32Value, StringValue… → nullable scalars"] any["Any / Struct → dynamic / unknown shapes"] util["Empty / FieldMask → no-arg calls & partial updates"]
The ones you’ll use constantly:
TimestampandDuration— a precise point in time and a span. Always prefer these over a rawint64of “milliseconds since something.”- Wrappers (
Int32Value,StringValue,BoolValue, …) — a message-typed box around a scalar, which gives you nullability. An older alternative to theoptionalkeyword; still common in existing APIs. Any— carries an arbitrary serialized message plus its type URL, for genuinely dynamic payloads. Powerful but it defeats static typing, so use it sparingly.Struct— arbitrary JSON-like data (objects, lists, values) when a shape truly isn’t known ahead of time.Empty— a message with no fields, for RPCs that take or return nothing.FieldMask— a list of field paths, the standard way to express “update only these fields” in a partial-update API.
import "google/protobuf/timestamp.proto";import "google/protobuf/field_mask.proto";
message Event { string name = 1; google.protobuf.Timestamp created_at = 2;}
message UpdateUserRequest { User user = 1; google.protobuf.FieldMask update_mask = 2; // which fields to change}