From 0b58d862ca166c0c17ce298b6770c9b636aa26fe Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Mon, 29 Aug 2022 02:25:18 +0100 Subject: [PATCH 1/6] GET_NAME_FIELD_BY_TAG: Added get name of field from it's json tag, added converting a map to a struct --- reflections.go | 39 +++++++++++++++++++++++++++++++++++++++ reflections_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/reflections.go b/reflections.go index b25b423..6988ac9 100644 --- a/reflections.go +++ b/reflections.go @@ -91,6 +91,26 @@ func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { return field.Tag.Get(tagKey), nil } +// Function to get the the Field from it's json tag +func GetNameFieldByTag(obj interface{}, tag string, key string) (string, error) { + if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + return "", errors.New("Cannot use GetFieldByTag on a non-struct interface") + } + + objValue := reflectValue(obj) + objType := objValue.Type() + fieldsCount := objType.NumField() + + for i := 0; i < fieldsCount; i++ { + structField := objType.Field(i) + if structField.Tag.Get(key) == tag { + return structField.Name, nil + } + } + + return "", errors.New("tag doesn't exist in the given struct") +} + // SetField sets the provided obj field with provided value. obj param has // to be a pointer to a struct, otherwise it will soundly fail. Provided // value type should match with the struct field you're trying to set. @@ -177,6 +197,25 @@ func fields(obj interface{}, deep bool) ([]string, error) { return allFields, nil } +// Converts a map datatype to struct datatype +// obj must be a pointer to a structure +func MapToStruct(obj map[string]interface{}, result interface{}) error { + if !hasValidType(obj, []reflect.Kind{reflect.Map}) { + return errors.New("Cannot use MapToStruct on a non-map interface") + } + + if !hasValidType(result, []reflect.Kind{reflect.Struct, reflect.Ptr}) { + return errors.New("result must be a pointer to a struct") + } + + t := reflect.ValueOf(result).Elem() + for k, v := range obj { + val := t.FieldByName(k) + val.Set(reflect.ValueOf(v)) + } + return nil +} + // Items returns the field - value struct pairs as a map. obj can whether // be a structure or pointer to structure. func Items(obj interface{}) (map[string]interface{}, error) { diff --git a/reflections_test.go b/reflections_test.go index 5546c46..e587d6b 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -443,6 +443,33 @@ func TestItems_deep(t *testing.T) { assert.Equal(t, itemsDeep["Number"], 17) } +func TestGetNameFieldByTag(t *testing.T) { + + dummyStruct := TestStruct{ + Dummy: "test", + Yummy: 123, + } + + tagJson := "dummytag" + field, err := GetNameFieldByTag(dummyStruct, tagJson, "test") + + assert.NoError(t, err) + assert.Equal(t, field, "Dummy") +} + +func TestGetNameFieldByTag_on_non_existing_tag(t *testing.T) { + + dummyStruct := TestStruct{ + Dummy: "test", + Yummy: 123, + } + + tagJson := "tag" + _, err := GetNameFieldByTag(dummyStruct, tagJson, "test") + + assert.Error(t, err) +} + func TestTags_deep(t *testing.T) { type Address struct { Street string `tag:"be"` @@ -504,3 +531,20 @@ func TestFields_deep(t *testing.T) { assert.Equal(t, fieldsDeep[1], "Street") assert.Equal(t, fieldsDeep[2], "Number") } + +func TestMapToStruct_on_map(t *testing.T) { + testMap := make(map[string]interface{}) + testMap["Dummy"] = "test" + testMap["Yummy"] = 123 + + dummyStruct := TestStruct{ + Dummy: "test", + Yummy: 123, + } + + var convertedMap TestStruct + err := MapToStruct(testMap, &convertedMap) + + assert.NoError(t, err) + assert.Equal(t, convertedMap, dummyStruct) +} From 1b6a64f337efcdc6c7eb413deb3b62859b21c4d6 Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Mon, 29 Aug 2022 02:42:29 +0100 Subject: [PATCH 2/6] GET_NAME_FIELD_BY_TAG: Typos in comment --- reflections.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflections.go b/reflections.go index 6988ac9..1bba256 100644 --- a/reflections.go +++ b/reflections.go @@ -198,7 +198,7 @@ func fields(obj interface{}, deep bool) ([]string, error) { } // Converts a map datatype to struct datatype -// obj must be a pointer to a structure +// result must be a pointer to a structure func MapToStruct(obj map[string]interface{}, result interface{}) error { if !hasValidType(obj, []reflect.Kind{reflect.Map}) { return errors.New("Cannot use MapToStruct on a non-map interface") From 1740dfd6aaa7b59197159b8ed1c7d1cc91a01158 Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Sun, 4 Sep 2022 19:49:39 +0100 Subject: [PATCH 3/6] GetFieldNameByTagValue: added example in example_test.go, removed MapToStruct and it's test, updated function name, added more test cases in reflections_test.go, added short example in Readme file --- README.md | 20 ++++++++++++++++++++ example_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ reflections.go | 26 ++++---------------------- reflections_test.go | 38 ++++++++++++++++---------------------- 4 files changed, 84 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 8f6b38d..09a99df 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,26 @@ unexported fields cannot be set, and that field type and value type have to matc err := reflection.SetField(&s, "FirstField", 123) // err != nil ``` +##### GetFieldNameByTagValue + +*GetFieldNameByTagValue* looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. +If `obj` is not a `struct`, nor a `pointer`, or it does not have a field tagged with the `tagKey`, and the matching `tagValue`, this function returns an error. + +```go + s := MyStruct { + FirstField: "first value", `matched:"first tag"` + SecondField: 2, `matched:"second tag"` + ThirdField: "third value", `unmatched:"third tag"` + } + + // Getting field name from external source as json would be headache to convert it manually, so we get it directly from struct tag + // returns fieldName = "FirstField" + fieldName, _ = reflections.GetFieldNameByTagValue(s, "first tag", "matched"); + + // later we can do GetField(s, fieldName) +``` + + ## Important notes * **unexported fields** cannot be accessed or set using reflections library: the golang reflect library intentionaly prohibits unexported fields values access or modifications. diff --git a/example_test.go b/example_test.go index c715b43..56d5a88 100644 --- a/example_test.go +++ b/example_test.go @@ -1,6 +1,7 @@ package reflections_test import ( + "encoding/json" "fmt" "log" "reflect" @@ -213,3 +214,46 @@ func ExampleSetField() { log.Fatal(err) } } + +func ExampleGetFieldNameByTagValue() { + type Order struct { + Step string `json:"order_step"` + Id string `json:"id"` + Category string `json:"category"` + } + type Condition struct { + Field string `json:"field"` + Value string `json:"value"` + Next string `json:"next"` + } + + // JSON data from external source + orderJson := `{ + "order_step": "cooking", + "id": "45457-fv54f54", + "category": "Pizzas" + }` + + conditionJson := `{ + "field": "order_step", + "value": "cooking", + "next": "serve" + }` + + // Storing Json in corresponding Variables + var order Order + json.Unmarshal([]byte(orderJson), &order) + + var condition Condition + json.Unmarshal([]byte(conditionJson), &condition) + + // example + // condition.Field = "order_step" + // we need to get this value order[condition.Field] + // but condition.Field in go needs to be "Step" not "order_step" + // this is what GetFieldNameByTagValue is about + // returns fieldName = "Step" + fieldName, _ := reflections.GetFieldNameByTagValue(condition, condition.Field, "json") + fieldValue, _ := reflections.GetField(order, fieldName) + fmt.Println(fieldValue) +} diff --git a/reflections.go b/reflections.go index 1bba256..b8170da 100644 --- a/reflections.go +++ b/reflections.go @@ -91,8 +91,9 @@ func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { return field.Tag.Get(tagKey), nil } -// Function to get the the Field from it's json tag -func GetNameFieldByTag(obj interface{}, tag string, key string) (string, error) { +// GetFieldNameByTagValue looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. +// If `obj` is not a `struct`, nor a `pointer`, or it does not have a field tagged with the `tagKey`, and the matching `tagValue`, this function returns an error. +func GetFieldNameByTagValue(obj interface{}, tagValue string, tagKey string) (string, error) { if !hasValidType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return "", errors.New("Cannot use GetFieldByTag on a non-struct interface") } @@ -103,7 +104,7 @@ func GetNameFieldByTag(obj interface{}, tag string, key string) (string, error) for i := 0; i < fieldsCount; i++ { structField := objType.Field(i) - if structField.Tag.Get(key) == tag { + if structField.Tag.Get(tagKey) == tagValue { return structField.Name, nil } } @@ -197,25 +198,6 @@ func fields(obj interface{}, deep bool) ([]string, error) { return allFields, nil } -// Converts a map datatype to struct datatype -// result must be a pointer to a structure -func MapToStruct(obj map[string]interface{}, result interface{}) error { - if !hasValidType(obj, []reflect.Kind{reflect.Map}) { - return errors.New("Cannot use MapToStruct on a non-map interface") - } - - if !hasValidType(result, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return errors.New("result must be a pointer to a struct") - } - - t := reflect.ValueOf(result).Elem() - for k, v := range obj { - val := t.FieldByName(k) - val.Set(reflect.ValueOf(v)) - } - return nil -} - // Items returns the field - value struct pairs as a map. obj can whether // be a structure or pointer to structure. func Items(obj interface{}) (map[string]interface{}, error) { diff --git a/reflections_test.go b/reflections_test.go index e587d6b..afb3ed3 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -446,12 +446,12 @@ func TestItems_deep(t *testing.T) { func TestGetNameFieldByTag(t *testing.T) { dummyStruct := TestStruct{ - Dummy: "test", + Dummy: "dummy", Yummy: 123, } tagJson := "dummytag" - field, err := GetNameFieldByTag(dummyStruct, tagJson, "test") + field, err := GetFieldNameByTagValue(dummyStruct, tagJson, "test") assert.NoError(t, err) assert.Equal(t, field, "Dummy") @@ -460,14 +460,25 @@ func TestGetNameFieldByTag(t *testing.T) { func TestGetNameFieldByTag_on_non_existing_tag(t *testing.T) { dummyStruct := TestStruct{ - Dummy: "test", + Dummy: "dummy", Yummy: 123, } + // non existing tag value with an existing tag key tagJson := "tag" - _, err := GetNameFieldByTag(dummyStruct, tagJson, "test") + _, errTagValue := GetFieldNameByTagValue(dummyStruct, tagJson, "test") + assert.Error(t, errTagValue) + + // non existing tag key with an existing tag value + tagJson = "dummytag" + _, errTagKey := GetFieldNameByTagValue(dummyStruct, tagJson, "json") + assert.Error(t, errTagKey) + + // non existing tag key and value + tagJson = "tag" + _, errTagKeyValue := GetFieldNameByTagValue(dummyStruct, tagJson, "json") + assert.Error(t, errTagKeyValue) - assert.Error(t, err) } func TestTags_deep(t *testing.T) { @@ -531,20 +542,3 @@ func TestFields_deep(t *testing.T) { assert.Equal(t, fieldsDeep[1], "Street") assert.Equal(t, fieldsDeep[2], "Number") } - -func TestMapToStruct_on_map(t *testing.T) { - testMap := make(map[string]interface{}) - testMap["Dummy"] = "test" - testMap["Yummy"] = 123 - - dummyStruct := TestStruct{ - Dummy: "test", - Yummy: 123, - } - - var convertedMap TestStruct - err := MapToStruct(testMap, &convertedMap) - - assert.NoError(t, err) - assert.Equal(t, convertedMap, dummyStruct) -} From ee7c56ab01769bdadd7c3725bab92b2393b57c7d Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Sun, 4 Sep 2022 19:53:31 +0100 Subject: [PATCH 4/6] GetFieldNameByTagValue: fixed some Readme Typos --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 09a99df..f365f27 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,8 @@ If `obj` is not a `struct`, nor a `pointer`, or it does not have a field tagged ThirdField: "third value", `unmatched:"third tag"` } - // Getting field name from external source as json would be headache to convert it manually, so we get it directly from struct tag + // Getting field name from external source as json would be a headache to convert it manually, + // so we get it directly from struct tag // returns fieldName = "FirstField" fieldName, _ = reflections.GetFieldNameByTagValue(s, "first tag", "matched"); From 4110f635de2c54215061f725ad277c7a648b7bfd Mon Sep 17 00:00:00 2001 From: Ala-Mansouri Date: Sun, 4 Sep 2022 20:10:03 +0100 Subject: [PATCH 5/6] GetFieldNameByTagValue: updated test names --- reflections_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reflections_test.go b/reflections_test.go index afb3ed3..871358d 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -443,7 +443,7 @@ func TestItems_deep(t *testing.T) { assert.Equal(t, itemsDeep["Number"], 17) } -func TestGetNameFieldByTag(t *testing.T) { +func TestGetFieldNameByTagValue(t *testing.T) { dummyStruct := TestStruct{ Dummy: "dummy", @@ -457,7 +457,7 @@ func TestGetNameFieldByTag(t *testing.T) { assert.Equal(t, field, "Dummy") } -func TestGetNameFieldByTag_on_non_existing_tag(t *testing.T) { +func TestGetFieldNameByTagValue_on_non_existing_tag(t *testing.T) { dummyStruct := TestStruct{ Dummy: "dummy", From 0b9d42bb9768985d07049a7a849fc8ebb16c0f89 Mon Sep 17 00:00:00 2001 From: oleiade Date: Mon, 5 Sep 2022 09:10:25 +0200 Subject: [PATCH 6/6] Fix param order, linter issues, and example --- example_test.go | 33 +++++++++++++++++++-------------- reflections.go | 7 ++++--- reflections_test.go | 19 ++++++++++--------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/example_test.go b/example_test.go index 56d5a88..2030854 100644 --- a/example_test.go +++ b/example_test.go @@ -218,7 +218,7 @@ func ExampleSetField() { func ExampleGetFieldNameByTagValue() { type Order struct { Step string `json:"order_step"` - Id string `json:"id"` + ID string `json:"id"` Category string `json:"category"` } type Condition struct { @@ -228,32 +228,37 @@ func ExampleGetFieldNameByTagValue() { } // JSON data from external source - orderJson := `{ + orderJSON := `{ "order_step": "cooking", "id": "45457-fv54f54", "category": "Pizzas" }` - conditionJson := `{ + conditionJSON := `{ "field": "order_step", "value": "cooking", "next": "serve" }` - // Storing Json in corresponding Variables + // Storing JSON in corresponding Variables var order Order - json.Unmarshal([]byte(orderJson), &order) + err := json.Unmarshal([]byte(orderJSON), &order) + if err != nil { + log.Fatal(err) + } var condition Condition - json.Unmarshal([]byte(conditionJson), &condition) - - // example - // condition.Field = "order_step" - // we need to get this value order[condition.Field] - // but condition.Field in go needs to be "Step" not "order_step" - // this is what GetFieldNameByTagValue is about - // returns fieldName = "Step" - fieldName, _ := reflections.GetFieldNameByTagValue(condition, condition.Field, "json") + err = json.Unmarshal([]byte(conditionJSON), &condition) + if err != nil { + log.Fatal(err) + } + + fieldName, _ := reflections.GetFieldNameByTagValue(order, "json", condition.Field) + fmt.Println(fieldName) fieldValue, _ := reflections.GetField(order, fieldName) fmt.Println(fieldValue) + + // Output: + // Step + // cooking } diff --git a/reflections.go b/reflections.go index 44bf720..f39e02e 100644 --- a/reflections.go +++ b/reflections.go @@ -102,10 +102,11 @@ func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) { } // GetFieldNameByTagValue looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. -// If `obj` is not a `struct`, nor a `pointer`, or it does not have a field tagged with the `tagKey`, and the matching `tagValue`, this function returns an error. -func GetFieldNameByTagValue(obj interface{}, tagValue string, tagKey string) (string, error) { +// The `obj` parameter must be a `struct`, or a `pointer` to one. If the `obj` parameter doesn't have a field tagged +// with the `tagKey`, and the matching `tagValue`, this function returns an error. +func GetFieldNameByTagValue(obj interface{}, tagKey, tagValue string) (string, error) { if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Ptr}) { - return "", errors.New("Cannot use GetFieldByTag on a non-struct interface") + return "", fmt.Errorf("cannot use GetFieldByTag on a non-struct interface: %w", ErrUnsupportedType) } objValue := reflectValue(obj) diff --git a/reflections_test.go b/reflections_test.go index 1b6eb0f..c601594 100644 --- a/reflections_test.go +++ b/reflections_test.go @@ -514,20 +514,22 @@ func TestItems_deep(t *testing.T) { } func TestGetFieldNameByTagValue(t *testing.T) { + t.Parallel() dummyStruct := TestStruct{ Dummy: "dummy", Yummy: 123, } - tagJson := "dummytag" - field, err := GetFieldNameByTagValue(dummyStruct, tagJson, "test") + tagJSON := "dummytag" + field, err := GetFieldNameByTagValue(dummyStruct, "test", tagJSON) assert.NoError(t, err) assert.Equal(t, field, "Dummy") } func TestGetFieldNameByTagValue_on_non_existing_tag(t *testing.T) { + t.Parallel() dummyStruct := TestStruct{ Dummy: "dummy", @@ -535,20 +537,19 @@ func TestGetFieldNameByTagValue_on_non_existing_tag(t *testing.T) { } // non existing tag value with an existing tag key - tagJson := "tag" - _, errTagValue := GetFieldNameByTagValue(dummyStruct, tagJson, "test") + tagJSON := "tag" + _, errTagValue := GetFieldNameByTagValue(dummyStruct, "test", tagJSON) assert.Error(t, errTagValue) // non existing tag key with an existing tag value - tagJson = "dummytag" - _, errTagKey := GetFieldNameByTagValue(dummyStruct, tagJson, "json") + tagJSON = "dummytag" + _, errTagKey := GetFieldNameByTagValue(dummyStruct, "json", tagJSON) assert.Error(t, errTagKey) // non existing tag key and value - tagJson = "tag" - _, errTagKeyValue := GetFieldNameByTagValue(dummyStruct, tagJson, "json") + tagJSON = "tag" + _, errTagKeyValue := GetFieldNameByTagValue(dummyStruct, "json", tagJSON) assert.Error(t, errTagKeyValue) - } //nolint:unused