From f755888c674e373db690e0e7d03f6838c66acd0c Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Wed, 12 Jun 2024 08:11:10 -0500 Subject: [PATCH] :bug: Fix jMap struct field tags preserved. (#652) Bug: File reaper json query: ``` SELECT count(*) FROM `Task` ,json_each(Attached) j0 WHERE json_extract(j0.value,'$.id')=file.pk ``` not finding references which result in false orphans. Fix: Ensure struct tags are honored and preserved by jMap. For Attachments: ``` [ {"Activity":9,"ID":351,"Name":"ssh-agent.output"}, {"Activity":14,"ID":352,"Name":"git.output"}, {"Activity":19,"ID":353,"Name":"windup-shim.output"}, {"Activity":20,"ID":354,"Name":"settings.yaml"}, {"Activity":21,"ID":355,"Name":"konveyor-analyzer.output"}, {"Activity":23,"ID":356,"Name":"konveyor-analyzer-dep.output"} ] ``` MUST be stored as (with lower-case keys): ``` [ {"activity":9,"id":351,"name":"ssh-agent.output"}, {"activity":14,"id":352,"name":"git.output"}, {"activity":19,"id":353,"name":"windup-shim.output"}, {"activity":20,"id":354,"name":"settings.yaml"}, {"activity":21,"id":355,"name":"konveyor-analyzer.output"}, {"activity":23,"id":356,"name":"konveyor-analyzer-dep.output"} ] ``` else not found. --------- Signed-off-by: Jeff Ortel --- model/model_test.go | 98 +++++++++++++++++++++++++++++++++++++++++++++ model/serializer.go | 35 ++++++++-------- 2 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 model/model_test.go diff --git a/model/model_test.go b/model/model_test.go new file mode 100644 index 000000000..e97fb16e1 --- /dev/null +++ b/model/model_test.go @@ -0,0 +1,98 @@ +package model + +import ( + "encoding/json" + "testing" + + "github.com/onsi/gomega" +) + +func TestMapMap(t *testing.T) { + g := gomega.NewGomegaWithT(t) + js := jsonSerializer{} + in := map[any]any{ + 0: "A", + "1": "B", + } + out := js.jMap(in) + mp, cast := out.(map[string]any) + g.Expect(cast).To(gomega.BeTrue()) + g.Expect(len(mp)).To(gomega.Equal(len(in))) + g.Expect(mp["0"]).To(gomega.Equal(in[0])) +} + +func TestMapArrayStruct(t *testing.T) { + g := gomega.NewGomegaWithT(t) + js := jsonSerializer{} + in := []Attachment{ + {ID: 1, Activity: 18}, + {ID: 2, Activity: 48}, + } + out := js.jMap(in) + list, cast := out.([]any) + g.Expect(cast).To(gomega.BeTrue()) + g.Expect(len(list)).To(gomega.Equal(len(in))) + g.Expect(list[0].(Attachment).ID).To(gomega.Equal(in[0].ID)) + g.Expect(list[1].(Attachment).ID).To(gomega.Equal(in[1].ID)) + g.Expect(list[0].(Attachment).Activity).To(gomega.Equal(in[0].Activity)) + g.Expect(list[1].(Attachment).Activity).To(gomega.Equal(in[1].Activity)) + + m, err := json.Marshal(list[0]) + g.Expect(err).To(gomega.BeNil()) + m2 := make(map[string]any) + err = json.Unmarshal(m, &m2) + g.Expect(err).To(gomega.BeNil()) + g.Expect(m2["id"]).To(gomega.Equal(float64(1))) + g.Expect(m2["activity"]).To(gomega.Equal(float64(18))) +} + +func TestMapData(t *testing.T) { + g := gomega.NewGomegaWithT(t) + js := jsonSerializer{} + in := Data{ + Any: map[any]any{ + 0: "A", + "1": "B", + }, + } + out := js.jMap(in) + d, cast := out.(Data) + g.Expect(cast).To(gomega.BeTrue()) + dAny, cast := d.Any.(map[string]any) + g.Expect(cast).To(gomega.BeTrue()) + g.Expect(len(dAny)).To(gomega.Equal(2)) + g.Expect(dAny["0"]).To(gomega.Equal(in.Any.(map[any]any)[0])) + g.Expect(dAny["1"]).To(gomega.Equal(in.Any.(map[any]any)["1"])) +} + +func TestMapDataPtr(t *testing.T) { + g := gomega.NewGomegaWithT(t) + js := jsonSerializer{} + in := &Data{ + Any: map[any]any{ + 0: "A", + "1": "B", + 2: Data{ + Any: map[any]any{ + 2: "2A", + "3": "3B", + }, + }, + }, + } + out := js.jMap(in) + d, cast := out.(Data) + g.Expect(cast).To(gomega.BeTrue()) + dAny, cast := d.Any.(map[string]any) + g.Expect(cast).To(gomega.BeTrue()) + g.Expect(len(dAny)).To(gomega.Equal(3)) + g.Expect(dAny["0"]).To(gomega.Equal(in.Any.(map[any]any)[0])) + g.Expect(dAny["1"]).To(gomega.Equal(in.Any.(map[any]any)["1"])) + d2Any := dAny["2"] + d2d, cast := d2Any.(Data) + g.Expect(cast).To(gomega.BeTrue()) + d2dAny, cast := d2d.Any.(map[string]any) + g.Expect(cast).To(gomega.BeTrue()) + g.Expect(d2dAny["2"]).To(gomega.Equal("2A")) + g.Expect(d2dAny["3"]).To(gomega.Equal("3B")) +} diff --git a/model/serializer.go b/model/serializer.go index 95c019f16..edd4ebb49 100644 --- a/model/serializer.go +++ b/model/serializer.go @@ -86,34 +86,35 @@ func (r jsonSerializer) jMap(in any) (out any) { } switch t.Kind() { case reflect.Struct: - mp := make(map[string]any) + out = reflect.New(t).Interface() + nt := reflect.TypeOf(out) + nt = nt.Elem() + nv := reflect.ValueOf(out) + nv = nv.Elem() for i := 0; i < t.NumField(); i++ { - t := t.Field(i) - v := v.Field(i) - if !t.IsExported() { + ft := t.Field(i) + fv := v.Field(i) + if !ft.IsExported() { continue } var object any - switch v.Kind() { + switch fv.Kind() { case reflect.Ptr: if !v.IsNil() { - object = v.Elem().Interface() + object = fv.Elem().Interface() } default: - object = v.Interface() + object = fv.Interface() } object = r.jMap(object) - if t.Anonymous { - if m, cast := object.(map[string]any); cast { - for k, v := range m { - mp[k] = v - } - } - } else { - mp[t.Name] = object - } + ft = nt.Field(i) + fv = nv.Field(i) + x := reflect.ValueOf(object) + fv.Set(x) } - out = mp + v = reflect.ValueOf(out) + v = v.Elem() + out = v.Interface() case reflect.Slice: list := make([]any, 0) for i := 0; i < v.Len(); i++ {