From 76482820f3615f4312c3f1b95fa458b69b6e4f23 Mon Sep 17 00:00:00 2001 From: shockerli Date: Thu, 11 Mar 2021 17:04:39 +0800 Subject: [PATCH] add func FieldE return the field value from map/struct, ignore the filed type --- cvte.go | 24 ++++++++++++++++++++++++ cvte_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/cvte.go b/cvte.go index e2f26bf..1d231a1 100644 --- a/cvte.go +++ b/cvte.go @@ -12,6 +12,7 @@ import ( ) var errConvFail = errors.New("convert failed") +var errFieldNotFound = errors.New("field not found") var formatOutOfLimitInt = "%w, out of max limit value(%d)" var formatOutOfLimitFloat = "%w, out of max limit value(%f)" var formatExtend = "%v, %w" @@ -392,6 +393,7 @@ func SliceE(val interface{}) (sl []interface{}, err error) { return nil, newErr(val, "slice") } +// return the values of struct fields, and deep find the embedded fields func deepStructValues(rt reflect.Type, rv reflect.Value) (sl []interface{}) { for j := 0; j < rv.NumField(); j++ { if rt.Field(j).Anonymous { @@ -403,6 +405,28 @@ func deepStructValues(rt reflect.Type, rv reflect.Value) (sl []interface{}) { return } +// FieldE return the field value from map/struct, ignore the filed type +func FieldE(val interface{}, field interface{}) (interface{}, error) { + sf := String(field) // match with the String of field, so field can be any type + _, rt, rv := Indirect(val) + + switch rt.Kind() { + case reflect.Map: // key of map + for _, key := range rv.MapKeys() { + if String(key.Interface()) == sf { + return rv.MapIndex(key).Interface(), nil + } + } + case reflect.Struct: // field of struct + vv := rv.FieldByName(sf) + if vv.IsValid() { + return vv.Interface(), nil + } + } + + return nil, fmt.Errorf("%w(%s)", errFieldNotFound, sf) +} + // Indirect returns the value with base type func Indirect(a interface{}) (val interface{}, rt reflect.Type, rv reflect.Value) { if a == nil { diff --git a/cvte_test.go b/cvte_test.go index 2f98dc5..0682fe8 100644 --- a/cvte_test.go +++ b/cvte_test.go @@ -14,6 +14,8 @@ import ( "github.com/shockerli/cvt" ) +// [test data] + // alias type: bool type AliasTypeBool bool @@ -64,6 +66,10 @@ type TestStructC struct { C1 string } +func (c TestStructC) String() string { + return c.C1 +} + type TestStructD struct { D1 int } @@ -73,6 +79,8 @@ type TestStructE struct { DD *TestStructD } +// [function tests] + func TestBoolE(t *testing.T) { tests := []struct { input interface{} @@ -1389,3 +1397,44 @@ func TestSliceE(t *testing.T) { assert.Equal(t, tt.expect, v, msg) } } + +func TestFieldE(t *testing.T) { + tests := []struct { + input interface{} + field interface{} + expect interface{} + isErr bool + }{ + {TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "D1", 1, false}, + {TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "DD", &TestStructD{D1: 2}, false}, + {TestStructB{B1: 1, TestStructC: TestStructC{C1: "c1"}}, "C1", "c1", false}, + {map[int]interface{}{123: "112233"}, "123", "112233", false}, + {map[int]interface{}{123: "112233"}, 123, "112233", false}, + {map[string]interface{}{"123": "112233"}, 123, "112233", false}, + {map[string]interface{}{"c": "ccc"}, TestStructC{C1: "c"}, "ccc", false}, + + // errors + {TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "", nil, true}, + {TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "Age", nil, true}, + {int(123), "Name", nil, true}, + {uint16(123), "Name", nil, true}, + {float64(12.3), "Name", nil, true}, + {func() {}, "Name", nil, true}, + } + + for i, tt := range tests { + msg := fmt.Sprintf( + "i = %d, input[%+v], field[%s], expect[%+v], isErr[%v]", + i, tt.input, tt.field, tt.expect, tt.isErr, + ) + + v, err := cvt.FieldE(tt.input, tt.field) + if tt.isErr { + assert.Error(t, err, msg) + continue + } + + assert.NoError(t, err, msg) + assert.Equal(t, tt.expect, v, msg) + } +}