From 8b61b1599f54e348785a94afadebd650f78b5b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=C3=A9n=C3=A9trier?= Date: Mon, 2 Dec 2024 15:01:27 +0100 Subject: [PATCH 1/2] [FIX] Additional tests/fixes for KVListDeserializerFromReflect Apparently, previous versions failed to realize that a []string was an instance of []any, which made things complicated. --- deserialize/deserialize_reflect_test.go | 33 ++++++++++++++++++++++++- deserialize/deserialize_test.go | 22 +++++++++++++++++ deserialize/json/json.go | 18 ++++++++++---- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/deserialize/deserialize_reflect_test.go b/deserialize/deserialize_reflect_test.go index 8ce1c9d..7509585 100644 --- a/deserialize/deserialize_reflect_test.go +++ b/deserialize/deserialize_reflect_test.go @@ -144,7 +144,6 @@ func TestNestedStructReflectKVDeserializer(t *testing.T) { assert.Equal(t, *deserialized, sample) } -// Not mandatory, but could be nice to have. func TestAnonymStructReflectKVDeserializer(t *testing.T) { type EmbeddedStruct struct { BBB string @@ -175,3 +174,35 @@ func TestAnonymStructReflectKVDeserializer(t *testing.T) { assert.NilError(t, err) assert.Equal(t, *deserialized, sample) } + +// A bug we encountered in previous versions: KVDeserializerFromReflect +// did not manage to see through an alias `[]TestType` = `[]string`. +func TestReflectKVDeserializerUnderlyingPrimitiveSlices(t *testing.T) { + type StringAlias string + type Int8Alias uint8 + type BoolAlias bool + type TestStruct struct { + Strings []StringAlias + Int8s []Int8Alias + Bools []BoolAlias + } + sample := TestStruct{ + Strings: []StringAlias{"abc"}, + Int8s: []Int8Alias{1, 2, 3}, + Bools: []BoolAlias{true, true, false}, + } + + deserializer, err := deserialize.MakeKVDeserializerFromReflect(deserialize.QueryOptions(""), reflect.TypeOf(sample)) + assert.NilError(t, err) + + kvList := map[string][]string{} + kvList["Strings"] = []string{"abc"} + kvList["Int8s"] = []string{"1", "2", "3"} + kvList["Bools"] = []string{"true", "true", "false"} + + deserialized := new(TestStruct) + reflectDeserialized := reflect.ValueOf(deserialized).Elem() + err = deserializer.DeserializeKVListTo(kvList, &reflectDeserialized) + assert.NilError(t, err) + assert.DeepEqual(t, *deserialized, sample) +} diff --git a/deserialize/deserialize_test.go b/deserialize/deserialize_test.go index 95b2180..cd709fe 100644 --- a/deserialize/deserialize_test.go +++ b/deserialize/deserialize_test.go @@ -1558,3 +1558,25 @@ func TestKVDeserializerFlattened(t *testing.T) { assert.DeepEqual(t, *found, expected) } + +// KVListDeserializer works with arrays with non-primitive types with a primitive Kind. +func TestKVDeserializeUnderlyingPrimitiveSlices(t *testing.T) { + type TestType string + type TestStruct struct { + TestField []TestType + } + + deserializer, err := deserialize.MakeKVListDeserializer[TestStruct](deserialize.QueryOptions("")) + assert.NilError(t, err) + + sample := TestStruct{ + TestField: []TestType{"abc"}, + } + + kvlist := make(map[string][]string) + kvlist["TestField"] = []string{"abc"} + + deserialized, err := deserializer.DeserializeKVList(kvlist) + assert.NilError(t, err) + assert.DeepEqual(t, *deserialized, sample) +} diff --git a/deserialize/json/json.go b/deserialize/json/json.go index 6a5f879..7b58695 100644 --- a/deserialize/json/json.go +++ b/deserialize/json/json.go @@ -41,14 +41,22 @@ func (v Value) AsDict() (shared.Dict, bool) { } } func (v Value) AsSlice() ([]shared.Value, bool) { - if wrapped, ok := v.wrapped.([]any); ok { - result := make([]shared.Value, len(wrapped)) - for i, value := range wrapped { - result[i] = Value{wrapped: value} + // We can't simply cast to `[]any`, as this doesn't work for e.g. `[]string`. + reflected := reflect.ValueOf(v.wrapped) + switch reflected.Type().Kind() { + case reflect.Array: + fallthrough + case reflect.Slice: + length := reflected.Len() + result := make([]shared.Value, length) + for i := 0; i < length; i++ { + value := reflected.Index(i) + result[i] = Value{wrapped: value.Interface()} } return result, true + default: + return nil, false } - return nil, false } func (v Value) Interface() any { return v.wrapped From c7558580e87dfd94d7bf3691d0160dff52bf19b9 Mon Sep 17 00:00:00 2001 From: David Teller Date: Tue, 3 Dec 2024 19:28:35 +0100 Subject: [PATCH 2/2] [FIX] kvlist deserializer shouldn't go through json --- deserialize/deserialize.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deserialize/deserialize.go b/deserialize/deserialize.go index 0a38bcc..b2dc0f3 100644 --- a/deserialize/deserialize.go +++ b/deserialize/deserialize.go @@ -315,13 +315,13 @@ type kvReflectDeserializer struct { } func (kvrd kvReflectDeserializer) DeserializeKVListTo(value kvlist.KVList, reflectOut *reflect.Value) error { - normalized := make(jsonPkg.JSON) + normalized := make(map[string]any) err := deListMapReflect(kvrd.typ, normalized, value, kvrd.options) if err != nil { return err } - err = kvrd.reflectDeserializer(reflectOut, normalized.AsValue()) + err = kvrd.reflectDeserializer(reflectOut, kvlist.MakeRootDict(normalized).AsValue()) if err != nil { return err }