Search code examples
goprotocol-buffersproto3

Unknown field error when unmarshaling a message with changed embedded type


Given a proto like this:

message Request {
    uint64 account_id = 1;
    message Foo{
        uint64 foo_id = 1;
    }
    repeated Foo foos = 2;

when I added a field called bar_id

message Request {
    uint64 account_id = 1;
    message Foo{
        uint64 foo_id = 1;
        uint64 bar_id = 2;
    }
    repeated Foo foos = 2;

I got an error when deserializing using the old client via proto.UnmarshalText(msg, request). The error is unknown field name "bar_id" in serviceA.Request_Foo.

I know there's been a lot of changes to unknown field handling in proto-3 BUT this is not expected since it seems forward-compatibility (new server send request to old client) is violated. Is this related to using an embedded type? What's the best way to update the server without forcing the client to update?


Solution

  • It looks like you are using github.com/golang/protobuf which is deprecated. Use google.golang.org/protobuf/encoding/prototext

    UPD: use DiscardUnknown

    (prototext.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(b, msg)
    

    pb.proto:

    syntax = "proto3";
    
    // protoc --go_out=. *.proto
    
    package pb;
    
    option go_package = "./pb";
    
    message RequestOld {
        uint64 account_id = 1;
        message Foo{
            uint64 foo_id = 1;
        }
        repeated Foo foos = 2;
    }
    
    message RequestNew {
        uint64 account_id = 1;
        message Foo{
            uint64 foo_id = 1;
            uint64 bar_id = 2;
        }
        repeated Foo foos = 2;
    }
    

    func:

    import "google.golang.org/protobuf/encoding/prototext"
    
    // marshal old message
    msgOld := &pb.RequestOld{
        AccountId: 1,
        Foos: []*pb.RequestOld_Foo{
            {
                FooId: 2,
            },
        },
    }
    
    log.Println("old:", msgOld.String())
    
    bOld, err := prototext.Marshal(msgOld)
    if err != nil {
        panic(err)
    }
    
    // unmarshal to new message
    msgNew := &pb.RequestNew{}
    if err := prototext.Unmarshal(bOld, msgNew); err != nil {
        panic(err)
    }
    
    log.Println("new:", msgNew.String())
    

    output:

    2021/04/07 old: account_id:1  foos:{foo_id:2}
    2021/04/07 new: account_id:1  foos:{foo_id:2}