From 0b5fdcc34625e4aa2b499e982fd49ecb089af585 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Wed, 7 Feb 2024 10:25:53 +0100 Subject: [PATCH] Zero destination struct in `convert.ToTyped` (#1178) ## Changes Not doing this means that the output struct is not a true representation of the `dyn.Value` and unrepresentable state (e.g. unexported fields) can be carried over across `convert.ToTyped` calls. ## Tests Unit tests. --- libs/dyn/convert/to_typed.go | 4 ++++ libs/dyn/convert/to_typed_test.go | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/libs/dyn/convert/to_typed.go b/libs/dyn/convert/to_typed.go index 209de12c..715d3f67 100644 --- a/libs/dyn/convert/to_typed.go +++ b/libs/dyn/convert/to_typed.go @@ -53,6 +53,10 @@ func ToTyped(dst any, src dyn.Value) error { func toTypedStruct(dst reflect.Value, src dyn.Value) error { switch src.Kind() { case dyn.KindMap: + // Zero the destination struct such that fields + // that aren't present in [src] are cleared. + dst.SetZero() + info := getStructInfo(dst.Type()) for k, v := range src.MustMap() { index, ok := info.Fields[k] diff --git a/libs/dyn/convert/to_typed_test.go b/libs/dyn/convert/to_typed_test.go index 3adc94c7..fd399b93 100644 --- a/libs/dyn/convert/to_typed_test.go +++ b/libs/dyn/convert/to_typed_test.go @@ -59,6 +59,27 @@ func TestToTypedStructOverwrite(t *testing.T) { assert.Equal(t, "baz", out.Bar) } +func TestToTypedStructClearFields(t *testing.T) { + type Tmp struct { + Foo string `json:"foo"` + Bar string `json:"bar,omitempty"` + } + + // Struct value with non-empty fields. + var out = Tmp{ + Foo: "baz", + Bar: "qux", + } + + // Value is an empty map. + v := dyn.V(map[string]dyn.Value{}) + + // The previously set fields should be cleared. + err := ToTyped(&out, v) + require.NoError(t, err) + assert.Equal(t, Tmp{}, out) +} + func TestToTypedStructAnonymousByValue(t *testing.T) { type Bar struct { Bar string `json:"bar"`