-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
egibs
committed
Dec 29, 2023
1 parent
bd3ffc4
commit 906695a
Showing
7 changed files
with
392 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
1.0.0 | ||
1.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package deepwalk | ||
|
||
import "reflect" | ||
|
||
// DeepSearch traverses a data structure and returns the value of the specified key | ||
// DeepSearch does not need to know the path to the specified key | ||
// Like DeepWalk, DeepSearch will return either the first, last, or all encountered values | ||
// associated with the provided `searchKey` | ||
func DeepSearch( | ||
obj interface{}, | ||
searchKey string, | ||
defaultVal string, | ||
returnVal string, | ||
) (interface{}, error) { | ||
// Return the default value if the object is empty or if the keys or return value are invalid | ||
if IsEmpty(obj) || !ValidKeys([]string{searchKey}) || !ValidReturnVal(returnVal) { | ||
return defaultVal, nil | ||
} | ||
|
||
// Return the object if there are no keys to traverse | ||
if len(searchKey) == 0 { | ||
return obj, nil | ||
} | ||
|
||
var foundList []interface{} | ||
|
||
search(obj, searchKey, &foundList) | ||
|
||
return HandleReturnVal(&foundList, defaultVal, returnVal) | ||
} | ||
|
||
// search traverses a data structure and returns the value of the specified key | ||
func search(obj interface{}, searchKey string, foundList *[]interface{}) { | ||
switch object := obj.(type) { | ||
case map[string]interface{}: | ||
deepSearchMap(object, searchKey, foundList) | ||
case []interface{}: | ||
deepSearchSlice(object, searchKey, foundList) | ||
default: | ||
deepSearchStruct(object, searchKey, foundList) | ||
} | ||
} | ||
|
||
// deepSearchMap handles the case where the object is a map | ||
func deepSearchMap( | ||
obj map[string]interface{}, | ||
searchKey string, | ||
foundList *[]interface{}, | ||
) { | ||
for key, value := range obj { | ||
if key == searchKey { | ||
*foundList = append(*foundList, value) | ||
} | ||
search(value, searchKey, foundList) | ||
} | ||
} | ||
|
||
// deepSearchSlice handles the case where the object is a slice | ||
func deepSearchSlice( | ||
obj []interface{}, | ||
searchKey string, | ||
foundList *[]interface{}, | ||
) { | ||
for _, item := range obj { | ||
search(item, searchKey, foundList) | ||
} | ||
} | ||
|
||
// deepSearchStruct handles the case where the object is a struct | ||
func deepSearchStruct( | ||
obj interface{}, | ||
searchKey string, | ||
foundList *[]interface{}, | ||
) { | ||
if r := reflect.ValueOf(obj); r.Kind() == reflect.Struct { | ||
for i := 0; i < r.NumField(); i++ { | ||
f := r.Field(i) | ||
if r.Type().Field(i).Name == searchKey { | ||
*foundList = append(*foundList, f.Interface()) | ||
} else { | ||
search(f.Interface(), searchKey, foundList) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package deepwalk | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestDeepSearch(t *testing.T) { | ||
type args struct { | ||
obj interface{} | ||
searchKey string | ||
defaultVal string | ||
returnVal string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want interface{} | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "Test case 1 - key found in map", | ||
args: args{ | ||
obj: map[string]interface{}{ | ||
"key1": "value1", | ||
"key2": "value2", | ||
}, | ||
searchKey: "key1", | ||
defaultVal: "default", | ||
returnVal: "first", | ||
}, | ||
want: "value1", | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Test case 2 - key not found in map", | ||
args: args{ | ||
obj: map[string]interface{}{ | ||
"key1": "value1", | ||
"key2": "value2", | ||
}, | ||
searchKey: "key3", | ||
defaultVal: "default", | ||
returnVal: "first", | ||
}, | ||
want: "default", | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Test case 3 - key found in nested map", | ||
args: args{ | ||
obj: map[string]interface{}{ | ||
"key1": "value1", | ||
"key2": map[string]interface{}{ | ||
"key3": "value3", | ||
}, | ||
}, | ||
searchKey: "key3", | ||
defaultVal: "default", | ||
returnVal: "first", | ||
}, | ||
want: "value3", | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Test case 4 - key not found in nested map", | ||
args: args{ | ||
obj: map[string]interface{}{ | ||
"key1": "value1", | ||
"key2": map[string]interface{}{ | ||
"key3": "value3", | ||
}, | ||
}, | ||
searchKey: "key4", | ||
defaultVal: "default", | ||
returnVal: "first", | ||
}, | ||
want: "default", | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Test case 7 - key found in struct", | ||
args: args{ | ||
obj: struct { | ||
Key1 string | ||
Key2 string | ||
}{ | ||
Key1: "value1", | ||
Key2: "value2", | ||
}, | ||
searchKey: "Key1", | ||
defaultVal: "default", | ||
returnVal: "first", | ||
}, | ||
want: "value1", | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Test case 8 - key not found in struct", | ||
args: args{ | ||
obj: struct { | ||
Key1 string | ||
Key2 string | ||
}{ | ||
Key1: "value1", | ||
Key2: "value2", | ||
}, | ||
searchKey: "Key3", | ||
defaultVal: "default", | ||
returnVal: "first", | ||
}, | ||
want: "default", | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Test case 9 - duplicate key found in map", | ||
args: args{ | ||
obj: map[string]interface{}{ | ||
"key1": "value1", | ||
"key2": map[string]interface{}{ | ||
"key1": "value2", | ||
}, | ||
}, | ||
searchKey: "key1", | ||
defaultVal: "default", | ||
returnVal: "all", | ||
}, | ||
want: []interface{}{"value1", "value2"}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := DeepSearch(tt.args.obj, tt.args.searchKey, tt.args.defaultVal, tt.args.returnVal) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("DeepSearch() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("DeepSearch() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.