From 23b18faca8bbb0d46a5eb32d16f401172d37239d Mon Sep 17 00:00:00 2001 From: Denise Li Date: Wed, 8 May 2024 13:42:59 -0400 Subject: [PATCH] feat: support omitempty in custom json de/encoder (#1441) Fixes https://github.com/TBD54566975/ftl/issues/1262 --- go-runtime/encoding/encoding.go | 17 ++++++++++++++++- go-runtime/encoding/encoding_test.go | 7 +++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/go-runtime/encoding/encoding.go b/go-runtime/encoding/encoding.go index f73040f9e3..4ddf175173 100644 --- a/go-runtime/encoding/encoding.go +++ b/go-runtime/encoding/encoding.go @@ -60,7 +60,8 @@ func encodeValue(ctx context.Context, v reflect.Value, w *bytes.Buffer) error { enc := v.Interface().(OptionMarshaler) //nolint:forcetypeassert return enc.Marshal(ctx, w, encodeValue) - //TODO: Remove once we support `omitempty` tag + // TODO(Issue #1439): remove this special case by removing all usage of + // json.RawMessage, which is not a type we support. case t == reflect.TypeFor[json.RawMessage](): data, err := json.Marshal(v.Interface()) if err != nil { @@ -137,6 +138,9 @@ func encodeStruct(ctx context.Context, v reflect.Value, w *bytes.Buffer) error { // (t == reflect.TypeOf((*any)(nil)).Elem() && fv.IsZero()) { // continue // } + if isTaggedOmitempty(v, i) && fv.IsZero() { + continue + } if afterFirst { w.WriteRune(',') } @@ -150,6 +154,17 @@ func encodeStruct(ctx context.Context, v reflect.Value, w *bytes.Buffer) error { return nil } +func isTaggedOmitempty(v reflect.Value, i int) bool { + tag := v.Type().Field(i).Tag + tagVals := strings.Split(tag.Get("json"), ",") + for _, tagVal := range tagVals { + if strings.TrimSpace(tagVal) == "omitempty" { + return true + } + } + return false +} + func encodeBytes(v reflect.Value, w *bytes.Buffer) error { data := base64.StdEncoding.EncodeToString(v.Bytes()) fmt.Fprintf(w, "%q", data) diff --git a/go-runtime/encoding/encoding_test.go b/go-runtime/encoding/encoding_test.go index 063558647e..34975bb466 100644 --- a/go-runtime/encoding/encoding_test.go +++ b/go-runtime/encoding/encoding_test.go @@ -32,6 +32,11 @@ func TestMarshal(t *testing.T) { type inner struct { FooBar string } + type validateOmitempty struct { + ShouldOmit string `json:",omitempty"` + ShouldntOmit string `json:""` + NotTagged string + } tests := []struct { name string input any @@ -58,6 +63,8 @@ func TestMarshal(t *testing.T) { {name: "Pointer", input: &struct{ String string }{"foo"}, err: `pointer types are not supported: *struct { String string }`}, {name: "SumType", input: struct{ D discriminator }{variant{"hello"}}, expected: `{"d":{"name":"Variant","value":{"message":"hello"}}}`}, {name: "UnregisteredSumType", input: struct{ D unregistered }{variant{"hello"}}, err: `the only supported interface types are enums or any, not encoding_test.unregistered`}, + {name: "OmitEmptyNotNull", input: validateOmitempty{"foo", "bar", "baz"}, expected: `{"shouldOmit":"foo","shouldntOmit":"bar","notTagged":"baz"}`}, + {name: "OmitEmptyNull", input: validateOmitempty{}, expected: `{"shouldntOmit":"","notTagged":""}`}, } tr := typeregistry.NewTypeRegistry()