From 24dd5c2cbbb0b92562d68e7c1eafd7a475a1ebfa Mon Sep 17 00:00:00 2001 From: gkits <56302678+gKits@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:26:24 +0100 Subject: [PATCH 1/5] feat(encoder): add key separation of nested struct fields An Encoder can has the option to separate the names of nested structs with a period when it constructs the values key during encoding. --- encoder.go | 24 ++++++++++++++++++------ encoder_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/encoder.go b/encoder.go index 52f2c10..9bdf9d0 100644 --- a/encoder.go +++ b/encoder.go @@ -11,8 +11,9 @@ type encoderFunc func(reflect.Value) string // Encoder encodes values from a struct into url.Values. type Encoder struct { - cache *cache - regenc map[reflect.Type]encoderFunc + cache *cache + regenc map[reflect.Type]encoderFunc + keySeparation bool } // NewEncoder returns a new Encoder with defaults. @@ -26,7 +27,7 @@ func NewEncoder() *Encoder { func (e *Encoder) Encode(src interface{}, dst map[string][]string) error { v := reflect.ValueOf(src) - return e.encode(v, dst) + return e.encode(v, dst, "") } // RegisterEncoder registers a converter for encoding a custom type. @@ -40,6 +41,14 @@ func (e *Encoder) SetAliasTag(tag string) { e.cache.tag = tag } +// ActivateKeySeparation causes the keys of nested struct fields to be separated +// with a period when encoding. +// +// Allows encoded values to be decoded again using schema.Decode. +func (e *Encoder) ActivateKeySeparation() { + e.keySeparation = true +} + // isValidStructPointer test if input value is a valid struct pointer. func isValidStructPointer(v reflect.Value) bool { return v.Type().Kind() == reflect.Ptr && v.Elem().IsValid() && v.Elem().Type().Kind() == reflect.Struct @@ -75,7 +84,7 @@ func isZero(v reflect.Value) bool { return v.Interface() == z.Interface() } -func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { +func (e *Encoder) encode(v reflect.Value, dst map[string][]string, prefix string) error { if v.Kind() == reflect.Ptr { v = v.Elem() } @@ -91,10 +100,13 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { if name == "-" { continue } + if prefix != "" && e.keySeparation { + name = prefix + "." + name + } // Encode struct pointer types if the field is a valid pointer and a struct. if isValidStructPointer(v.Field(i)) && !e.hasCustomEncoder(v.Field(i).Type()) { - err := e.encode(v.Field(i).Elem(), dst) + err := e.encode(v.Field(i).Elem(), dst, name) if err != nil { errors[v.Field(i).Elem().Type().String()] = err } @@ -115,7 +127,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { } if v.Field(i).Type().Kind() == reflect.Struct { - err := e.encode(v.Field(i), dst) + err := e.encode(v.Field(i), dst, name) if err != nil { errors[v.Field(i).Type().String()] = err } diff --git a/encoder_test.go b/encoder_test.go index 092f0de..3f3b0ae 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -523,3 +523,32 @@ func TestRegisterEncoderWithPtrType(t *testing.T) { valExists(t, "DateStart", ss.DateStart.time.String(), vals) valExists(t, "DateEnd", "", vals) } + +func TestSeparation(t *testing.T) { + type outter struct { + Inner inner + } + type nested struct { + Outter outter + } + + nest := nested{ + Outter: outter{ + Inner: inner{ + F12: 12, + }, + }, + } + + encoder := NewEncoder() + encoder.ActivateKeySeparation() + + vals := map[string][]string{} + err := encoder.Encode(nest, vals) + + t.Log(vals) + + noError(t, err) + valsLength(t, 1, vals) + valExists(t, "Outter.Inner.F12", "12", vals) +} From 6d713824cb57c7642bfd27a6b13c6d188ab4e64e Mon Sep 17 00:00:00 2001 From: gKits <56302678+gKits@users.noreply.github.com> Date: Fri, 29 Nov 2024 07:16:58 +0100 Subject: [PATCH 2/5] style(encoder): rename option to KeyOriginalPath --- encoder.go | 18 +++++++++--------- encoder_test.go | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/encoder.go b/encoder.go index 9bdf9d0..20fa9f3 100644 --- a/encoder.go +++ b/encoder.go @@ -11,9 +11,9 @@ type encoderFunc func(reflect.Value) string // Encoder encodes values from a struct into url.Values. type Encoder struct { - cache *cache - regenc map[reflect.Type]encoderFunc - keySeparation bool + cache *cache + regenc map[reflect.Type]encoderFunc + keyOriginalPath bool } // NewEncoder returns a new Encoder with defaults. @@ -41,12 +41,12 @@ func (e *Encoder) SetAliasTag(tag string) { e.cache.tag = tag } -// ActivateKeySeparation causes the keys of nested struct fields to be separated -// with a period when encoding. +// KeyOriginalPath causes the keys of nested struct fields to keep their full original +// name consisting of each field name in its path separated with a period. // -// Allows encoded values to be decoded again using schema.Decode. -func (e *Encoder) ActivateKeySeparation() { - e.keySeparation = true +// Allows from nested struct encoded values to be decoded again using schema.Decode. +func (e *Encoder) KeyOriginalPath(originalKeyPath bool) { + e.keyOriginalPath = originalKeyPath } // isValidStructPointer test if input value is a valid struct pointer. @@ -100,7 +100,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string, prefix string if name == "-" { continue } - if prefix != "" && e.keySeparation { + if prefix != "" && e.keyOriginalPath { name = prefix + "." + name } diff --git a/encoder_test.go b/encoder_test.go index 3f3b0ae..c5913b8 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -524,7 +524,7 @@ func TestRegisterEncoderWithPtrType(t *testing.T) { valExists(t, "DateEnd", "", vals) } -func TestSeparation(t *testing.T) { +func TestEncoder_WithOriginalKeyPath(t *testing.T) { type outter struct { Inner inner } @@ -541,7 +541,7 @@ func TestSeparation(t *testing.T) { } encoder := NewEncoder() - encoder.ActivateKeySeparation() + encoder.OriginalKeyPath(true) vals := map[string][]string{} err := encoder.Encode(nest, vals) From 59b34c2914fd8e6be4b49af39889f05f70225669 Mon Sep 17 00:00:00 2001 From: gKits <56302678+gKits@users.noreply.github.com> Date: Fri, 29 Nov 2024 07:23:52 +0100 Subject: [PATCH 3/5] style(encoder): change name of test function for consistency --- encoder_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/encoder_test.go b/encoder_test.go index c5913b8..d755a88 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -524,7 +524,7 @@ func TestRegisterEncoderWithPtrType(t *testing.T) { valExists(t, "DateEnd", "", vals) } -func TestEncoder_WithOriginalKeyPath(t *testing.T) { +func TestEncoderKeyOriginalPath(t *testing.T) { type outter struct { Inner inner } @@ -541,7 +541,7 @@ func TestEncoder_WithOriginalKeyPath(t *testing.T) { } encoder := NewEncoder() - encoder.OriginalKeyPath(true) + encoder.KeyOriginalPath(true) vals := map[string][]string{} err := encoder.Encode(nest, vals) From 1235ee229bc138440154d0effceca45740d96993 Mon Sep 17 00:00:00 2001 From: gKits <56302678+gKits@users.noreply.github.com> Date: Fri, 29 Nov 2024 07:51:30 +0100 Subject: [PATCH 4/5] test(encoder): add test case for KeyOriginalPath with struct pointer --- encoder_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/encoder_test.go b/encoder_test.go index d755a88..120d6f8 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -526,7 +526,8 @@ func TestRegisterEncoderWithPtrType(t *testing.T) { func TestEncoderKeyOriginalPath(t *testing.T) { type outter struct { - Inner inner + Inner inner + InnerPtr *inner } type nested struct { Outter outter @@ -537,6 +538,9 @@ func TestEncoderKeyOriginalPath(t *testing.T) { Inner: inner{ F12: 12, }, + InnerPtr: &inner{ + F12: 12, + }, }, } @@ -549,6 +553,7 @@ func TestEncoderKeyOriginalPath(t *testing.T) { t.Log(vals) noError(t, err) - valsLength(t, 1, vals) + valsLength(t, 2, vals) valExists(t, "Outter.Inner.F12", "12", vals) + valExists(t, "Outter.InnerPtr.F12", "12", vals) } From 330e825067bef12259b8501cf4602104d12a4577 Mon Sep 17 00:00:00 2001 From: gKits <56302678+gKits@users.noreply.github.com> Date: Fri, 29 Nov 2024 14:32:37 +0100 Subject: [PATCH 5/5] style: rename parameter for consistency --- encoder.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/encoder.go b/encoder.go index 20fa9f3..04d5d39 100644 --- a/encoder.go +++ b/encoder.go @@ -45,8 +45,8 @@ func (e *Encoder) SetAliasTag(tag string) { // name consisting of each field name in its path separated with a period. // // Allows from nested struct encoded values to be decoded again using schema.Decode. -func (e *Encoder) KeyOriginalPath(originalKeyPath bool) { - e.keyOriginalPath = originalKeyPath +func (e *Encoder) KeyOriginalPath(keyOriginalPath bool) { + e.keyOriginalPath = keyOriginalPath } // isValidStructPointer test if input value is a valid struct pointer.