diff --git a/accounting/grpc/service_frostfs.pb.go b/accounting/grpc/service_frostfs.pb.go index 4f11303..9c8c946 100644 Binary files a/accounting/grpc/service_frostfs.pb.go and b/accounting/grpc/service_frostfs.pb.go differ diff --git a/accounting/grpc/types_frostfs.pb.go b/accounting/grpc/types_frostfs.pb.go index 8585583..b7e6b40 100644 Binary files a/accounting/grpc/types_frostfs.pb.go and b/accounting/grpc/types_frostfs.pb.go differ diff --git a/acl/grpc/types_frostfs.pb.go b/acl/grpc/types_frostfs.pb.go index 1ba6c53..e5a6f32 100644 Binary files a/acl/grpc/types_frostfs.pb.go and b/acl/grpc/types_frostfs.pb.go differ diff --git a/ape/grpc/types_frostfs.pb.go b/ape/grpc/types_frostfs.pb.go index 7967e13..cf9499f 100644 Binary files a/ape/grpc/types_frostfs.pb.go and b/ape/grpc/types_frostfs.pb.go differ diff --git a/apemanager/grpc/service_frostfs.pb.go b/apemanager/grpc/service_frostfs.pb.go index 2a30850..7f3ed10 100644 Binary files a/apemanager/grpc/service_frostfs.pb.go and b/apemanager/grpc/service_frostfs.pb.go differ diff --git a/container/grpc/service_frostfs.pb.go b/container/grpc/service_frostfs.pb.go index 4ccb595..cc05dde 100644 Binary files a/container/grpc/service_frostfs.pb.go and b/container/grpc/service_frostfs.pb.go differ diff --git a/container/grpc/types_frostfs.pb.go b/container/grpc/types_frostfs.pb.go index e5da35f..4ca23fa 100644 Binary files a/container/grpc/types_frostfs.pb.go and b/container/grpc/types_frostfs.pb.go differ diff --git a/lock/grpc/types_frostfs.pb.go b/lock/grpc/types_frostfs.pb.go index 33f22c5..004a01f 100644 Binary files a/lock/grpc/types_frostfs.pb.go and b/lock/grpc/types_frostfs.pb.go differ diff --git a/netmap/grpc/service_frostfs.pb.go b/netmap/grpc/service_frostfs.pb.go index 4d53031..9ebbf98 100644 Binary files a/netmap/grpc/service_frostfs.pb.go and b/netmap/grpc/service_frostfs.pb.go differ diff --git a/netmap/grpc/types_frostfs.pb.go b/netmap/grpc/types_frostfs.pb.go index a4b10f9..0328531 100644 Binary files a/netmap/grpc/types_frostfs.pb.go and b/netmap/grpc/types_frostfs.pb.go differ diff --git a/object/grpc/service_frostfs.pb.go b/object/grpc/service_frostfs.pb.go index f934f3b..9ee5a3d 100644 Binary files a/object/grpc/service_frostfs.pb.go and b/object/grpc/service_frostfs.pb.go differ diff --git a/object/grpc/types_frostfs.pb.go b/object/grpc/types_frostfs.pb.go index 0ee939a..e4a916f 100644 Binary files a/object/grpc/types_frostfs.pb.go and b/object/grpc/types_frostfs.pb.go differ diff --git a/refs/grpc/types_frostfs.pb.go b/refs/grpc/types_frostfs.pb.go index 3ed647c..5a16f8e 100644 Binary files a/refs/grpc/types_frostfs.pb.go and b/refs/grpc/types_frostfs.pb.go differ diff --git a/session/grpc/service_frostfs.pb.go b/session/grpc/service_frostfs.pb.go index c71325a..6d3ea9f 100644 Binary files a/session/grpc/service_frostfs.pb.go and b/session/grpc/service_frostfs.pb.go differ diff --git a/session/grpc/types_frostfs.pb.go b/session/grpc/types_frostfs.pb.go index e18c1ab..e9f3b42 100644 Binary files a/session/grpc/types_frostfs.pb.go and b/session/grpc/types_frostfs.pb.go differ diff --git a/status/grpc/types_frostfs.pb.go b/status/grpc/types_frostfs.pb.go index 609fc03..1d8b477 100644 Binary files a/status/grpc/types_frostfs.pb.go and b/status/grpc/types_frostfs.pb.go differ diff --git a/tombstone/grpc/types_frostfs.pb.go b/tombstone/grpc/types_frostfs.pb.go index e5b67d5..acb1836 100644 Binary files a/tombstone/grpc/types_frostfs.pb.go and b/tombstone/grpc/types_frostfs.pb.go differ diff --git a/util/proto/marshal_test.go b/util/proto/marshal_test.go index 8b717be..5498cf9 100644 --- a/util/proto/marshal_test.go +++ b/util/proto/marshal_test.go @@ -26,11 +26,37 @@ func nonZero[T protoInt]() T { func TestStableMarshalSingle(t *testing.T) { t.Run("empty", func(t *testing.T) { - input := &generated.Primitives{} - require.Zero(t, input.StableSize()) + t.Run("proto", func(t *testing.T) { + input := &generated.Primitives{} + require.Zero(t, input.StableSize()) - r := input.MarshalProtobuf(nil) - require.Empty(t, r) + r := input.MarshalProtobuf(nil) + require.Empty(t, r) + }) + t.Run("json", func(t *testing.T) { + input := &generated.Primitives{} + r, err := input.MarshalJSON() + require.NoError(t, err) + require.NotEmpty(t, r) + + var actual test.Primitives + require.NoError(t, protojson.Unmarshal(r, &actual)) + + t.Run("protojson compatibility", func(t *testing.T) { + data, err := protojson.MarshalOptions{EmitUnpopulated: true}.Marshal(&actual) + require.NoError(t, err) + require.JSONEq(t, string(data), string(r)) + }) + + var actualFrostfs generated.Primitives + require.NoError(t, actualFrostfs.UnmarshalJSON(r)) + if len(actualFrostfs.FieldA) == 0 { + actualFrostfs.FieldA = nil + } + require.Equal(t, input, &actualFrostfs) + + primitivesEqual(t, input, &actual) + }) }) marshalCases := []struct { @@ -77,13 +103,16 @@ func TestStableMarshalSingle(t *testing.T) { require.NoError(t, protojson.Unmarshal(r, &actual)) t.Run("protojson compatibility", func(t *testing.T) { - data, err := protojson.Marshal(&actual) + data, err := protojson.MarshalOptions{EmitUnpopulated: true}.Marshal(&actual) require.NoError(t, err) require.JSONEq(t, string(data), string(r)) }) var actualFrostfs generated.Primitives require.NoError(t, actualFrostfs.UnmarshalJSON(r)) + if len(actualFrostfs.FieldA) == 0 { + actualFrostfs.FieldA = nil + } require.Equal(t, tc.input, &actualFrostfs) primitivesEqual(t, tc.input, &actual) @@ -94,7 +123,10 @@ func TestStableMarshalSingle(t *testing.T) { func primitivesEqual(t *testing.T, a *generated.Primitives, b *test.Primitives) { // Compare each field directly, because proto-generated code has private fields. - require.Equal(t, a.FieldA, b.FieldA) + require.Equal(t, len(a.FieldA), len(b.FieldA)) + if len(a.FieldA) != 0 { + require.Equal(t, a.FieldA, b.FieldA) + } require.Equal(t, a.FieldB, b.FieldB) require.Equal(t, a.FieldC, b.FieldC) require.Equal(t, a.FieldD, b.FieldD) diff --git a/util/proto/test/custom/test_frostfs.pb.go b/util/proto/test/custom/test_frostfs.pb.go index 3804e46..f979038 100644 Binary files a/util/proto/test/custom/test_frostfs.pb.go and b/util/proto/test/custom/test_frostfs.pb.go differ diff --git a/util/protogen/internalgengo/json.go b/util/protogen/internalgengo/json.go index 3267955..15facfd 100644 --- a/util/protogen/internalgengo/json.go +++ b/util/protogen/internalgengo/json.go @@ -192,14 +192,17 @@ func emitJSONFieldWrite(g *protogen.GeneratedFile, f *protogen.Field, name strin selector := name + "." + f.GoName - isNotDefault := notNil - if f.Desc.IsList() { - isNotDefault = notEmpty - } else if f.Desc.Kind() != protoreflect.MessageKind { - _, isNotDefault = easyprotoKindInfo(f.Desc.Kind()) - } - g.P("if ", isNotDefault(selector), "{") - defer g.P("}") + // This code is responsible for ignoring default values. + // We will restore it after having parametrized JSON marshaling. + // + // isNotDefault := notNil + // if f.Desc.IsList() { + // isNotDefault = notEmpty + // } else if f.Desc.Kind() != protoreflect.MessageKind { + // _, isNotDefault = easyprotoKindInfo(f.Desc.Kind()) + // } + // g.P("if ", isNotDefault(selector), "{") + // defer g.P("}") g.P("if !first { out.RawByte(','); } else { first = false; }") g.P("const prefix string = ", `"\"`, fieldJSONName(f), `\":"`) @@ -247,7 +250,10 @@ func emitJSONFieldWrite(g *protogen.GeneratedFile, f *protogen.Field, name strin case protoreflect.StringKind: template = "out.String(%s)" case protoreflect.BytesKind: - template = "out.Base64Bytes(%s)" + g.P("if ", selector, "!= nil {") + g.P("out.Base64Bytes(", selector, ")") + g.P("} else { out.String(\"\") }") + return case protoreflect.MessageKind: template = "%s.MarshalEasyJSON(out)" case protoreflect.GroupKind: