Search code examples
goprotocol-buffersmarshallingproto

How to use `omitempty` with protobuf Timestamp in Golang


I have an optional field on my struct called ExpireTime. It has a time.Time type and a json:"expire_time,omitempty" tag to not send it, when it is empty. This part works perfectly fine.

When I want to use the same field via GRPC, I run into an issue when converting it to the protobuf timestamp format.

type Timestamp struct {

    // Represents seconds of UTC time since Unix epoch
    // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
    // 9999-12-31T23:59:59Z inclusive.
    Seconds int64 `protobuf:"varint,1,opt,name=seconds,proto3" json:"seconds,omitempty"`
    // Non-negative fractions of a second at nanosecond resolution. Negative
    // second values with fractions must still have non-negative nanos values
    // that count forward in time. Must be from 0 to 999,999,999
    // inclusive.
    Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
    // contains filtered or unexported fields
}
ExpireTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=expire_time,json=expireTime,proto3" json:"expire_time,omitempty"`

The issue is that an empty time.Time{} object will be converted to a negative seconds value corresponding to 0001-01-01T00:00:00Z. Having the omitEmpty flag will not be applied in this case as the value is not zeroed out. What could I do to omit this field, when it is actually empty? Thanks!


Solution

  • As you say time.Time{} converts to 0001-01-01T00:00:00Z; this is working as intended. Note that you also need to be careful converting in the opposite direction (a zero TimeStamp will become 1970-01-01T00:00:00Z).

    However generally the Timestamp will be part of a message, for example:

    message MyMessage{
       google.protobuf.Timestamp  comm_time = 1;
    }
    

    Running this through protoc will result in something like:

    type MyMessage struct {
        state         protoimpl.MessageState
        sizeCache     protoimpl.SizeCache
        unknownFields protoimpl.UnknownFields
    
        CommTime *timestamppb.Timestamp `protobuf:"bytes, 1,opt,name=comm_time,json=commTime,proto3" json:"comm_time,omitempty"`
    }
    

    This means you should be able to achieve the result you are looking for with CommTime=nil; e.g.

    sourceTime := time.Time{}  // Whatever time you want to encode
    var commTime *timestamp.Timestamp
    if !sourceTime.IsZero() {
            commTime = timestamppb.New(sourceTime)
    }
    
    msg := MyMessage{
       CommTime: commTime,
    }