ChaChaPoly
ChaChaPoly

Reputation: 1851

Defining custom go struct tags for protobuf message fields

I am new to grpc and have been trying to just fetch a json response from a webserver. The stub can then request the json from the rpc server.

In my .proto file, I created a message type:

message Post {
    int64 number = 1;
    string now = 2;
    string name = 3;
}

But I am not able to marshal the number field, since protoc produces the struct pb.go file with a number tag:

{
        "no": "23",
        "now": "12:06:46",
        "name": "bob"
}

How can I force the Message to be 'converted' with a tag other than the lowercase name of the message field? Such as using the json tag no, even if the field name in the Message is number.

Upvotes: 14

Views: 26860

Answers (3)

hao luo
hao luo

Reputation: 29

Here is my solution, which is an friendly and compatible way for adding go struct tags in protobuf/grpc.

  1. Fork protobuf-go repo to your own github account, and add a go tags feature into cmd/protoc-gen-go. Like this: https://github.com/hacksomecn/protobuf-go/commit/2443a0ee4696acaa9aa4bf8c2e0586d7c724c645

  2. Install new-feature protoc-gen-go into you path. Like: go install github.com/hacksomecn/protobuf-go/cmd/protoc-gen-go@"v1.28.0-gotags"

  3. Declare message field with an tailing comment, add go tags expr in comment. Go tags expr regexp format is (\s?)@go_tags\(` + "(`.*`)" + `\)\s. Like:

message HelloGoTags {
  string Name = 1; // @go_tags(`json:"name,omitempty" yaml:"name" bson:"name" db:"name" gorm:"name" validate:"required"`) awesome name
}
  1. Use protoc compile .proto. Like:
protoc --go_out=. --go_opt=paths=source_relative  tags.proto
  1. Message HelloGoTags will be generated with extra tags. Like:
type HelloGoTags struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"name,omitempty" gorm:"name" validate:"required" yaml:"name" bson:"name" db:"name"` // awesome name
}

Upvotes: 0

Jack
Jack

Reputation: 169

import "github.com/gogo/protobuf/gogoproto/gogo.proto";

// Result example:
// type Post struct {
//    Number int64 `protobuf:"bytes,1,opt,name=number,json=no1,proto3" json:"no2"`
// }
message Post {
    int64 number = 1 [json_name="no1", (gogoproto.jsontag) = "no2"];
}

,where:

  • no1 - new name for jsonpb marshal/unmarshal
  • no2 - new name for json marshal/unmarshal

jsonpb example:

import (
    "bytes"
    "testing"
    "encoding/json"

    "github.com/golang/protobuf/jsonpb"
    "github.com/stretchr/testify/require"
)

func TestJSON(t *testing.T) {
    msg := &Post{
        Number: 1,
    }

    buf := bytes.NewBuffer(nil)

    require.NoError(t, (&jsonpb.Marshaler{}).Marshal(buf, msg))
    require.Equal(t, `{"no1":1}`, buf.String())

    buf.Truncate(0)

    require.NoError(t, json.NewEncoder(buf).Encode(msg))
    require.Equal(t, `{"no2":1}`, buf.String())
}

More information about protobuf extensions

Upvotes: 13

Zak
Zak

Reputation: 5898

You can set a proto3 field option on the proto message definition with a json_name

message Post {
    int64 number = 1 [json_name="no"];
    string now = 2;
    string name = 3;
}

link to the docs

Upvotes: 17

Related Questions