golang / protobuf

Go support for Google's protocol buffers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Reading value from proto struct via protoreflect for optional fields

honzap opened this issue · comments

Hello,

I have more over a question about the behavior of protoreflect.Value for optional fields in proto message.

Let's have a simple message:

message Foo {
  optional string param = 1;
}

This one generates Go code struct like this:

type Foo struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Param *string `protobuf:"bytes,1,opt,name=param,proto3,oneof" json:"param,omitempty"`
}

The Param here is nillable as expected.

When I try to read the value of that field via protoreflect, I'm basically not able to get the fact that the Param is nil. I'd expect that Value.IsValid() returns false because it can operate with nilType.

Let's have this test snippet:

foo := &Foo{}
descriptor := foo.ProtoReflect().Descriptor()
paramField := descriptor.Fields().ByTextName("param")

value := foo.ProtoReflect().Get(paramField)

assert.Equal(t, false, value.IsValid())
assert.Equal(t, nil, value.Interface())

I'd expect that this test will pass but IsValid returns true and calling Value.Interface() returns empty string "" (probably a default value of that field).

My goal is to know that the value is nil for the field Param. I achieved the result by this code, but I'm not sure it's the correct one:

isNil := !foo.ProtoReflect().Has(paramField) && paramField.HasOptionalKeyword()
assert.Equal(t, true, isNil)

Could you please explain me this behavior and why there is nilType not used in this case?

Thanks a lot.

Best,

Honza

protoreflect.Message.Get behaves mostly equivalently to accessor methods, which return the default value for unpopulated scalar fields. For example, in your case foo.GetField() returns "" if the field field is unset.

You can test to see if a field is set with foo.ProtoReflect().Has(paramField).

The protoreflect package operates on the protobuf data model, not the Go data model, so it doesn't have a concept of a field being nil. (For example, in generated messages, the representation of an unset optional string field is (*string)(nil), but a dynamicpb.Message uses a different representation. protoreflect can work with either.)

Thanks for explanation. So my line of code getting to know isNil is basically correct, right?

For me, the curious thing is that protoreflect.Value inside works with nilType but basically, this can never happen, right?

The internal implementation of protoreflect.Value may need to consider things like a value being nil as it is sort of the “glue” between the protobuf data model, and the Go data model. But it only surfaces strictly the protobuf data model, and the internal Go data model is strictly an obscured implementation detail.

Thank you for clarification. I'm closing this topic now.