Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GET_NAME_FIELD_BY_TAG: Added get name of field from it's json tag, ad… #16

Merged
merged 8 commits into from
Sep 5, 2022
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,27 @@ _ := reflections.SetField(&s, "FirstField", "new value")
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 a 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

- **un-exported fields** can't be accessed nor set using the `reflections` library. The Go lang standard `reflect` library intentionally prohibits un-exported fields values access or modifications.
Expand Down
49 changes: 49 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package reflections_test

import (
"encoding/json"
"fmt"
"log"
"reflect"
Expand Down Expand Up @@ -213,3 +214,51 @@ 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
err := json.Unmarshal([]byte(orderJSON), &order)
if err != nil {
log.Fatal(err)
}

var condition Condition
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
}
22 changes: 22 additions & 0 deletions reflections.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,28 @@ func GetFieldTag(obj interface{}, fieldName, tagKey string) (string, error) {
return field.Tag.Get(tagKey), nil
}

// GetFieldNameByTagValue looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item.
// 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 "", fmt.Errorf("cannot use GetFieldByTag on a non-struct interface: %w", ErrUnsupportedType)
}

objValue := reflectValue(obj)
objType := objValue.Type()
fieldsCount := objType.NumField()

for i := 0; i < fieldsCount; i++ {
structField := objType.Field(i)
if structField.Tag.Get(tagKey) == tagValue {
return structField.Name, nil
}
}

return "", errors.New("tag doesn't exist in the given struct")
}

// SetField sets the provided obj field with provided value.
//
// The `obj` parameter must be a pointer to a struct, otherwise it soundly fails.
Expand Down
39 changes: 39 additions & 0 deletions reflections_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,45 @@ func TestItems_deep(t *testing.T) {
assert.Equal(t, itemsDeep["Number"], 17)
}

func TestGetFieldNameByTagValue(t *testing.T) {
t.Parallel()

dummyStruct := TestStruct{
Dummy: "dummy",
Yummy: 123,
}

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",
Yummy: 123,
}

// non existing tag value with an existing tag key
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, "json", tagJSON)
assert.Error(t, errTagKey)

// non existing tag key and value
tagJSON = "tag"
_, errTagKeyValue := GetFieldNameByTagValue(dummyStruct, "json", tagJSON)
assert.Error(t, errTagKeyValue)
}

//nolint:unused
func TestTags_deep(t *testing.T) {
t.Parallel()
Expand Down