This AIP is currently under review. This means that the editors have read it and are in high-level concurrence, and while it is not yet fully approved, we have a good faith expectation that the AIP will be approved in something close to its current state.

AIP-2717

Patterns for generic fields

Developers have several options for how to represent generic values in proto messages. There are reasons to choose one over the other. Understanding them will lead to better and more consistent APIs.

Guidance

APIs should follow a consistent application of oneof vs. map<string, Foo> vs. Any vs. Struct.

oneof

A oneof is used to create a restriction on a set of optional fields, enforcing that only one of them may be set (these fields are still separate individual fields). A common pattern is to have a message that contains a single oneof collection of various message types. Such a oneof message is conceptually similar to a C union, or C++ std::variant. These should be used in most places where a generic message type is needed, in preference to other approaches.

Note: Adding additional possible values to an existing oneof is a non-breaking change, but moving existing fields into or out of a oneof is breaking (it creates a backwards-incompatible change in Go protobuf stubs).

map<string, Foo>

If a more generic structure is needed, a map of strings to objects may be used. Such a map is represented by a normal JSON object, such as {"a": "foo", "b": "bar"}. The downside to such maps is that they are limited to flat structures, and they can be difficult to work with because many string constants may be needed.

Any

An Any allows any message to be packed into the field. This is conceptually similar to a "bytes" field containing a serialized message. The advantage of an Any is that a user-defined proto message can be stored along with type information. The user must be able to know which kind of object is in the Any, which complicates the design. The disadvantage is that working with and debugging any protos is much more complicated since the code may or may not know how to deserialize the packed message. Also, the developer must contend with unexpected types of messages in the Any.

Any should not be used as a request parameter. Request parameters will either be a fixed set of types, in which case a oneof should be used, or a type descriptor would need to be sent along, in which case a struct should be used, which achieves essentially the same thing with much simpler semantics.

Struct

The Struct message can be used to represent arbitrary nested JSON. For example, given the following code:

Struct.Builder builder = Struct.newBuilder();
Value town = Value.newBuilder().setStringValue("Springfield").build();
Value population = Value.newBuilder().setNumberValue(273).build();
builder.putFields("town", town);
builder.putFields("population", population);
Struct survey = builder.build();

survey would serialize as the actual JSON {"town": "Springfield": "population": 273}. This message type is fairly uncommon, and should only be used rarely.