Skip to content

Commit

Permalink
Merge pull request #16 from Mansouri147/GET_NAME_FIELD_BY_TAG
Browse files Browse the repository at this point in the history
GET_NAME_FIELD_BY_TAG: Added get name of field from it's json tag, ad…
  • Loading branch information
oleiade authored Sep 5, 2022
2 parents f74d60e + 0b9d42b commit d6f33f5
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 0 deletions.
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

0 comments on commit d6f33f5

Please sign in to comment.