1

Since it is quiet trouble some in protobuf to define a flexible structure in some scenario.

for example:

message Foo {
   int a = 1;
   repeated int a_other = 2;
}

In fact, I don't want the service's client to pass the a and the a_other at the same time. However in protobuf, we can't put these two fields in oneof because the a_other is a list field. So with the above message declared, when we only pass the a_other field, the client side cannot tell if the a field is actually 0 or not passed by the server.

So I wonder if define a string field for Foo like:

message Foo {
   string data = 1;
}

and both the server side and the client side is agreed to treat the data field as a JSON. So the server dumps the primitive data and the client loads it, and they live happily ever after.

But my question is, is it a common practice to do that? And what is the disadvantage of it? (this way drop the type check of course) Or is there a better way?

ItsJingran
  • 147
  • 9
  • You can put the `a` field by itself in an oneof, or use https://github.com/protocolbuffers/protobuf/blob/master/docs/implementing_proto3_presence.md – jpa Mar 03 '22 at 18:00
  • `optional` might also work for you (the implementation is effectively the same as a single item `oneof`) - see [this answer](https://stackoverflow.com/a/62566052/11810946). If you want to pass JSON that is an option but you loose some of the key benefits to using protobuf (type safety, `proto` file fully defines interface, code generation etc). – Brits Mar 03 '22 at 22:30
  • it looks like the `optional` does not support `repeated` field either. @Brits – ItsJingran Mar 04 '22 at 01:38
  • If you want to do this for a repeated field then wrap it in another message (e.g. `message ints {repeated int i = 1;}`) and then use that as an `optional`/`oneof` (basically the approach taken in `google/protobuf/wrappers.proto`). – Brits Mar 04 '22 at 01:48

1 Answers1

1

Repeated fields in oneof are generally solved by a wrapper message. In your case:

message Foo {
  oneof choose_one_a {
    int a = 1;
    AOtherMessage a_other = 2;
  }
}

message AOtherMessage {
  repeated int a_other = 1;
}

It seems you can easily stop there.

However, if you do ever find yourself in the "I want arbitrary JSON here" situation, then you would probably want to consider struct.proto's Value instead of a plain string. Value can hold an arbitrary JSON value. If you want to hold an arbitrary JSON object (a.k.a., a map) then use Struct instead. Types in struct.proto are well-known types known to protobuf and when converting a protobuf message to JSON, they are represented in native JSON form.

message Foo {
  google.protobuf.Value a = 1;
}

However, before "dropping the schema and swapping to JSON," you should consult the different ways of handling dynamic content.

Eric Anderson
  • 24,057
  • 5
  • 55
  • 76