Using Other Message
We also can use other Message type in an Message type, like this:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
Also, if we want to use another Message type from other .proto
file, how to do that? We can use import
to solve this problem:
// new.proto
// Some definitions...
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
The protocol compiler searches for imported files in a set of directories specified on the protocol compiler command line using the -I
/--proto_path
flag. In general you should set the --proto_path
flag to the root of your project and use fully qualified names for all imports.
Nested Type
We can also define another Message type in an Message type, like this:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
And we can reuse the Result
Message type towrad .
:
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
Updating a Message Type
In protobuf, there is a way that can help you update the message types without breaking any of your existing code, to achieve this, you need to obey these rules:
- Don’t change the field numbers for any existing fields.
- If you add new fields, any messages serialized by code using your “old” message format can still be parsed by your new generated code. You should keep in mind the default values for these elements so that new code can properly interact with messages generated by old code.
- Fields can be removed, as long as the field number is not used again in your updated message type.
int32
,uint32
,int64
,uint64
, andbool
are all compatible – this means you can change a field from one of these types to another without breaking forwards- or backwards-compatibility.sint32
andsint64
are compatible with each other but are not compatible with the other integer types.string
andbytes
are compatible as long as the bytes are valid UTF-8.- Embedded messages are compatible with
bytes
if the bytes contain an encoded version of the message. fixed32
is compatible withsfixed32
, andfixed64
withsfixed64
.enum
is compatible withint32
,uint32
,int64
, anduint64
in terms of wire format (note that values will be truncated if they don’t fit).- Changing a single value into a member of a new
oneof
is safe and binary compatible. But moving any fields into an existingoneof
is not safe.
Any
The Any
message type lets you use messages as embedded types without having their .proto
definition. But you need to import google/protobuf/any.proto
to use Any
type:
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
Different language implementations will support runtime library helpers to pack and unpack Any values in a typesafe manner, for example, in C++ there are PackFrom()
and UnpackTo()
methods:
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
Oneof
If you have a message with many fields and where at most one field will be set at the same time, you can enforce this behavior and save memory by using the oneof
feature.
You can use a special case()
or WhichOneof()
method to check which value in a oneof
is set.
How to use Oneof?
To define Oneof
, we need to use the oneof
keyword:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
You then add your oneof fields to the oneof definition. You can add fields of any type, but cannot use repeated
fields.
Oneof features?
-
Setting a oneof field will automatically clear all other members of the oneof.
-
If the parser encounters multiple members of the same oneof on the wire, only the last member seen is used in the parsed message.
-
A oneof cannot be
repeated
. -
Reflection APIs work for oneof fields.
-
If you set a oneof field to the default value (such as setting an
int32
oneof field to 0), the “case” of that oneof field will be set, and the value will be serialized on the wire. -
If you’re using C++, make sure your code doesn’t cause memory crashes. The following sample code will crash because
sub_message
was already deleted by calling theset_name()
method. -
SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here
-
Again in C++, if you
Swap()
two messages with oneofs, each message will end up with the other’s oneof case: in the example below,msg1
will have asub_message
andmsg2
will have aname
. -
SampleMessage msg1; msg1.set_name("name"); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK(msg2.has_name());
Backwards-compatibility issues
Be careful when adding or removing oneof fields. If checking the value of a oneof returns None
/NOT_SET
, it could mean that the oneof has not been set or it has been set to a field in a different version of the oneof.
Tag reuse issues
- Move fields into or out of a oneof: You may lose some of your information (some fields will be cleared) after the message is serialized and parsed.
- Delete a oneof field and add it back: This may clear your currently set oneof field after the message is serialized and parsed.
- Split or merge oneof: This has similar issues to moving regular fields.