danielgtaylor / python-betterproto

Clean, modern, Python 3.6+ code generator & library for Protobuf 3 and async gRPC
MIT License
1.45k stars 196 forks source link

Default values are not serialized in oneofs when calling .to_dict() or .to_json() (which is different behaviour from official protobuf impl) #109

Open dhendry opened 4 years ago

dhendry commented 4 years ago

Consider this protobuf message:

message OneOfDemo{
    oneof value_type {
        bool bool_value = 1;
        int64 int64_value = 2;
    }
}

Using the official protobuf codegen and library (libprotoc 3.12.3):

from google.protobuf.json_format import MessageToDict

# Official protobuf:
print("True: ", json.dumps(MessageToDict(OneOfDemo(bool_value=True))))
print("False:", json.dumps(MessageToDict(OneOfDemo(bool_value=False))))
print("1:    ", json.dumps(MessageToDict(OneOfDemo(int64_value=1))))
print("0:    ", json.dumps(MessageToDict(OneOfDemo(int64_value=0))))

Output:
True:  {"boolValue": true}
False: {"boolValue": false}
1:     {"int64Value": "1"}
0:     {"int64Value": "0"}

Using betterproto (1.2.5)

# Generated code: 
@dataclass
class OneOfDemoBetter(betterproto.Message):
    bool_value: bool = betterproto.bool_field(1, group="value_type")
    int64_value: int = betterproto.int64_field(2, group="value_type")

print("True: ", OneOfDemoBetter(bool_value=True).to_json())
print("False:", OneOfDemoBetter(bool_value=False).to_json())
print("1:    ", OneOfDemoBetter(int64_value=1).to_json())
print("0:    ", OneOfDemoBetter(int64_value=0).to_json())

Output:
True:  {"boolValue": true}
False: {}
1:     {"int64Value": "1"}
0:     {}

This obviously leads to the following inconsistency:

od = OneOfDemoBetter(bool_value=False)
print(betterproto.which_one_of(od, "value_type"))

od2 = OneOfDemoBetter().from_json(od.to_json())
print(betterproto.which_one_of(od2, "value_type"))

Output:
('bool_value', False)
('', None)

Misc

Great work on this project! Keep it up!

bradykieffer commented 4 years ago

I hacked together a proposed fix for this in https://github.com/danielgtaylor/python-betterproto/pull/110. I'm also really impressed with this project!

boukeversteegh commented 4 years ago

See also:

Thank you for your great write-up! This makes a clear case to include the default values in the output.

I'll respond further in your PR 😄

boukeversteegh commented 4 years ago

As for your other suggestions, they are all valuable ideas. As it will be hard to track them being inside this issue, if you like, please create a proposal for those so we can categorize / prioritize them, and discuss them separately.

sach-edna commented 3 years ago

I'm not sure whether this is the correct place to comment, but I've noticed that the default values are also not serialized when calling bytes() on a dataclass for a oneoff.

I have a message with a oneoff field. One of the options of the oneoff is a submessage with two required fields, an enum field and a uint32 field. when calling bytes(message) with the default value (which is 0) on the uint32 field, it is not added to the binary data.

When I do the same using the default python protobuff implementation this field is added to the binary data.

enum Enum {
    a = 1;
    b = 2;
};

enum OtherEnum {
    c = 3;
    d = 4;
};

message First{
    required OtherEnum enum_name  = 1;
    required uint32 uint32_name = 2;
};

message Second{
    required uint32 offset = 1;
    required bytes data   = 2;
}
message MyMessage{
    required Enum myEnum = 1;

    oneof params {
        First first = 2;
        Second second = 3;
    }
};

So in the case of MyMessage(myEnum=Enum.a, first=First(enum_name=OtherEnum.d, uint32_name =0) when bytes is called, the uint32_name field is not present in the binary data even though it is required.

If this belongs in a seperate issue please let me know.