-
Notifications
You must be signed in to change notification settings - Fork 36
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
1 parent
4a4cbd9
commit b8a1c85
Showing
2 changed files
with
192 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package jsonutil | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
) | ||
|
||
// FilterJSON filters the input JSON based on the include and exclude fields. | ||
// - data: the original JSON data as a byte slice. | ||
// - includeFields: list of fields to include (if empty, includes all). | ||
// - excludeFields: list of fields to exclude (processed after include). | ||
func FilterJSON(data []byte, includeFields, excludeFields []string) ([]byte, error) { | ||
var raw map[string]interface{} | ||
if err := json.Unmarshal(data, &raw); err != nil { | ||
return nil, errors.New("invalid JSON input") | ||
} | ||
|
||
filtered := make(map[string]interface{}) | ||
|
||
includeMap := make(map[string]bool) | ||
excludeMap := make(map[string]bool) | ||
|
||
for _, field := range includeFields { | ||
includeMap[field] = true | ||
} | ||
for _, field := range excludeFields { | ||
excludeMap[field] = true | ||
} | ||
|
||
if len(includeMap) > 0 { | ||
for field := range includeMap { | ||
if val, exists := raw[field]; exists { | ||
filtered[field] = val | ||
} | ||
} | ||
} else { | ||
for key, val := range raw { | ||
filtered[key] = val | ||
} | ||
} | ||
|
||
for field := range excludeMap { | ||
delete(filtered, field) | ||
} | ||
|
||
return json.Marshal(filtered) | ||
} | ||
|
||
// GetJSONFields returns all the top-level fields from the given JSON data. | ||
// - data: the original JSON data as a byte slice. | ||
// Returns a slice of field names or an error if the JSON is invalid. | ||
func GetJSONFields(data []byte) ([]string, error) { | ||
var raw map[string]interface{} | ||
if err := json.Unmarshal(data, &raw); err != nil { | ||
return nil, errors.New("invalid JSON input") | ||
} | ||
|
||
fields := make([]string, 0, len(raw)) | ||
for key := range raw { | ||
fields = append(fields, key) | ||
} | ||
|
||
return fields, nil | ||
} |
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,128 @@ | ||
package jsonutil | ||
|
||
import ( | ||
"encoding/json" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestFilterJSON(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
input string | ||
includeFields []string | ||
excludeFields []string | ||
want string | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "include specific fields", | ||
input: `{"name":"John","age":30,"city":"New York"}`, | ||
includeFields: []string{"name", "age"}, | ||
excludeFields: []string{}, | ||
want: `{"age":30,"name":"John"}`, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "exclude specific fields", | ||
input: `{"name":"John","age":30,"city":"New York"}`, | ||
includeFields: []string{}, | ||
excludeFields: []string{"age"}, | ||
want: `{"city":"New York","name":"John"}`, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "include and exclude", | ||
input: `{"name":"John","age":30,"city":"New York","country":"USA"}`, | ||
includeFields: []string{"name", "age", "city"}, | ||
excludeFields: []string{"age"}, | ||
want: `{"city":"New York","name":"John"}`, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "invalid JSON", | ||
input: `{"name":"John"`, | ||
includeFields: []string{}, | ||
excludeFields: []string{}, | ||
want: "", | ||
wantErr: true, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
got, err := FilterJSON([]byte(tc.input), tc.includeFields, tc.excludeFields) | ||
|
||
if (err != nil) != tc.wantErr { | ||
t.Errorf("FilterJSON() error = %v, wantErr %v", err, tc.wantErr) | ||
return | ||
} | ||
|
||
if !tc.wantErr { | ||
var gotMap, wantMap map[string]interface{} | ||
json.Unmarshal(got, &gotMap) | ||
json.Unmarshal([]byte(tc.want), &wantMap) | ||
|
||
if !reflect.DeepEqual(gotMap, wantMap) { | ||
t.Errorf("FilterJSON() = %v, want %v", string(got), tc.want) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestGetJSONFields(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
input string | ||
want []string | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "valid JSON", | ||
input: `{"name":"John","age":30,"city":"New York"}`, | ||
want: []string{"name", "age", "city"}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "empty JSON", | ||
input: `{}`, | ||
want: []string{}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "invalid JSON", | ||
input: `{"name":"John"`, | ||
want: nil, | ||
wantErr: true, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
got, err := GetJSONFields([]byte(tc.input)) | ||
|
||
if (err != nil) != tc.wantErr { | ||
t.Errorf("GetJSONFields() error = %v, wantErr %v", err, tc.wantErr) | ||
return | ||
} | ||
|
||
if !tc.wantErr { | ||
// Sort both slices to ensure consistent comparison | ||
gotMap := make(map[string]bool) | ||
wantMap := make(map[string]bool) | ||
|
||
for _, field := range got { | ||
gotMap[field] = true | ||
} | ||
for _, field := range tc.want { | ||
wantMap[field] = true | ||
} | ||
|
||
if !reflect.DeepEqual(gotMap, wantMap) { | ||
t.Errorf("GetJSONFields() = %v, want %v", got, tc.want) | ||
} | ||
} | ||
}) | ||
} | ||
} |