Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"errors"
"reflect"
"strings"

mapsutil "github.com/projectdiscovery/utils/maps"
)

// CallbackFunc on the struct field
Expand Down Expand Up @@ -100,8 +102,8 @@ func FilterStruct[T any](input T, includeFields, excludeFields []string) (T, err
return filteredStruct.Interface().(T), nil
}

func FilterStructToMap[T any](input T, includeFields, excludeFields []string) (map[string]any, error) {
resultMap := make(map[string]any)
func FilterStructToMap[T any](input T, includeFields, excludeFields []string) (*mapsutil.OrderedMap[string, any], error) {
resultMap := mapsutil.NewOrderedMap[string, any]()

walker := func(field reflect.StructField, value reflect.Value) {
jsonTag := field.Tag.Get("json")
Expand All @@ -116,14 +118,14 @@ func FilterStructToMap[T any](input T, includeFields, excludeFields []string) (m
return
}

resultMap[jsonKey] = fieldValue
resultMap.Set(jsonKey, fieldValue)
}

if err := walkFilteredFields(input, includeFields, excludeFields, walker); err != nil {
return nil, err
}

return resultMap, nil
return &resultMap, nil
}

// GetStructFields returns all the top-level field names from the given struct.
Expand Down
136 changes: 117 additions & 19 deletions structs/structs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package structs
import (
"reflect"
"testing"

mapsutil "github.com/projectdiscovery/utils/maps"
)

type TestStruct struct {
Expand Down Expand Up @@ -103,53 +105,61 @@ func TestFilterStructToMap(t *testing.T) {
input any
includeFields []string
excludeFields []string
want map[string]any
want *mapsutil.OrderedMap[string, any]
wantErr bool
}{
{
name: "no filtering",
input: s,
includeFields: nil,
excludeFields: nil,
want: map[string]any{
"name": "John",
"age": 30,
"is_active": true,
"address": "New York",
},
want: func() *mapsutil.OrderedMap[string, any] {
om := mapsutil.NewOrderedMap[string, any]()
om.Set("name", "John")
om.Set("age", 30)
om.Set("is_active", true)
om.Set("address", "New York")
return &om
}(),
wantErr: false,
},
{
name: "include specific fields",
input: s,
includeFields: []string{"Name", "Address"},
excludeFields: []string{},
want: map[string]any{
"name": "John",
"address": "New York",
},
want: func() *mapsutil.OrderedMap[string, any] {
om := mapsutil.NewOrderedMap[string, any]()
om.Set("name", "John")
om.Set("address", "New York")
return &om
}(),
wantErr: false,
},
{
name: "exclude specific fields",
input: s,
includeFields: []string{},
excludeFields: []string{"Address", "IsActive"},
want: map[string]any{
"name": "John",
"age": 30,
},
want: func() *mapsutil.OrderedMap[string, any] {
om := mapsutil.NewOrderedMap[string, any]()
om.Set("name", "John")
om.Set("age", 30)
return &om
}(),
wantErr: false,
},
{
name: "include and exclude",
input: s,
includeFields: []string{"Name", "Age", "Address"},
excludeFields: []string{"Age"},
want: map[string]any{
"name": "John",
"address": "New York",
},
want: func() *mapsutil.OrderedMap[string, any] {
om := mapsutil.NewOrderedMap[string, any]()
om.Set("name", "John")
om.Set("address", "New York")
return &om
}(),
wantErr: false,
},
{
Expand All @@ -176,6 +186,94 @@ func TestFilterStructToMap(t *testing.T) {
}
}

func TestFilterStructToMapOrder(t *testing.T) {
type OrderTestStruct struct {
FirstField string `json:"first_field"`
SecondField int `json:"second_field"`
ThirdField bool `json:"third_field"`
FourthField string `json:"fourth_field"`
}

s := OrderTestStruct{
FirstField: "first",
SecondField: 42,
ThirdField: true,
FourthField: "fourth",
}

t.Run("field order follows struct definition", func(t *testing.T) {
result, err := FilterStructToMap(s, nil, nil)
if err != nil {
t.Fatalf("FilterStructToMap() error = %v", err)
}

keys := result.GetKeys()
expectedOrder := []string{"first_field", "second_field", "third_field", "fourth_field"}

if len(keys) != len(expectedOrder) {
t.Errorf("Expected %d keys, got %d", len(expectedOrder), len(keys))
}

for i, expectedKey := range expectedOrder {
if i >= len(keys) {
t.Errorf("Missing key at index %d: expected %s", i, expectedKey)
continue
}
if keys[i] != expectedKey {
t.Errorf("Key at index %d: expected %s, got %s", i, expectedKey, keys[i])
}
}
})

t.Run("field order preserved with include filter", func(t *testing.T) {
result, err := FilterStructToMap(s, []string{"ThirdField", "FirstField"}, nil)
if err != nil {
t.Fatalf("FilterStructToMap() error = %v", err)
}

keys := result.GetKeys()
expectedOrder := []string{"first_field", "third_field"} // Struct order, not include order

if len(keys) != len(expectedOrder) {
t.Errorf("Expected %d keys, got %d", len(expectedOrder), len(keys))
}

for i, expectedKey := range expectedOrder {
if i >= len(keys) {
t.Errorf("Missing key at index %d: expected %s", i, expectedKey)
continue
}
if keys[i] != expectedKey {
t.Errorf("Key at index %d: expected %s, got %s", i, expectedKey, keys[i])
}
}
})

t.Run("field order preserved with exclude filter", func(t *testing.T) {
result, err := FilterStructToMap(s, nil, []string{"SecondField"})
if err != nil {
t.Fatalf("FilterStructToMap() error = %v", err)
}

keys := result.GetKeys()
expectedOrder := []string{"first_field", "third_field", "fourth_field"}

if len(keys) != len(expectedOrder) {
t.Errorf("Expected %d keys, got %d", len(expectedOrder), len(keys))
}

for i, expectedKey := range expectedOrder {
if i >= len(keys) {
t.Errorf("Missing key at index %d: expected %s", i, expectedKey)
continue
}
if keys[i] != expectedKey {
t.Errorf("Key at index %d: expected %s, got %s", i, expectedKey, keys[i])
}
}
})
}

func TestGetStructFields(t *testing.T) {
s := TestStruct{
Name: "John",
Expand Down
Loading