diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 4898b09..eddce51 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -7,11 +7,13 @@ on: - "**.go" - "go.mod" - "go.sum" + - ".github/workflows/go-ci.yml" pull_request: paths: - "**.go" - "go.mod" - "go.sum" + - ".github/workflows/go-ci.yml" concurrency: group: goflat-go-ci-${{ github.ref }} @@ -19,9 +21,7 @@ concurrency: jobs: sast: - uses: notdodo/github-actions/.github/workflows/go-security-scan.yml@2e84638563b65587b42ba8ab87ccdf1922c412dd - # go-sec-v0.0.0 + uses: notdodo/github-actions/.github/workflows/go-security-scan.yml@go-sec-v0 build-and-test: - uses: notdodo/github-actions/.github/workflows/go-ci.yml@2e84638563b65587b42ba8ab87ccdf1922c412dd - # go-ci-v0.0.0 + uses: notdodo/github-actions/.github/workflows/go-ci.yml@go-ci-v0 diff --git a/goflat.go b/goflat.go index efccbeb..644951c 100644 --- a/goflat.go +++ b/goflat.go @@ -22,12 +22,12 @@ type FlattenerConfig struct { } // `DefaultFlattenerConfig` returns a FlattenerConfig with default values. -func defaultConfiguration(config ...FlattenerConfig) FlattenerConfig { +func defaultConfiguration() FlattenerConfig { return FlattenerConfig{ Prefix: "", Separator: ".", - OmitEmpty: false, - OmitNil: false, + OmitEmpty: true, + OmitNil: true, SortKeys: false, KeysToLower: false, } @@ -206,7 +206,14 @@ func flattenFields(val reflect.Value, prefix string, result map[string]interface // If the value is neither a struct nor a map, add it to the result map. // Optionally omitting empty or nil values based on the configuration. if !(config.OmitEmpty && isEmptyValue(val)) && !(config.OmitNil && isNilValue(val)) { - result[prefix[:len(prefix)-1]] = val.Interface() + prefix = prefix[:len(prefix)-1] + // If `val` is a valid JSON likely this was *string; flat it + if js := isJSON(val.String()); js != nil { + flatMap, _ := FlatJSONToMap(val.String(), config) + flatten(prefix, flatMap, result, config) + } else { + result[prefix] = val.Interface() + } } } } @@ -260,3 +267,12 @@ func isNilValue(field reflect.Value) bool { // Check if the field is a pointer and is nil. return field.Kind() == reflect.Ptr && field.IsNil() } + +// `jsJSON` checks if a string it's a valid JSON string. +func isJSON(str string) json.RawMessage { + var js json.RawMessage + if err := json.Unmarshal([]byte(str), &js); err != nil { + return nil + } + return js +} diff --git a/goflat_test.go b/goflat_test.go index c370e29..0b622ca 100644 --- a/goflat_test.go +++ b/goflat_test.go @@ -17,9 +17,10 @@ type User struct { } type Member struct { - User *User - Role string - Active bool + User *User + Role string + Active bool + SubField *string } type Group struct { @@ -94,8 +95,24 @@ func TestIsEmptyValue(t *testing.T) { } func TestFlattenStructWithArrayOfPointersInGroup(t *testing.T) { + s := `[{ + "PolicyName": "policy-s3-operator", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::personal-s3-bucket/*" + ] + } + ] + }]` + members := []*Member{ - {User: &User{Username: "john_doe", Email: "john@example.com"}, Role: "Admin", Active: true}, + {User: &User{Username: "john_doe", Email: "john@example.com"}, Role: "Admin", Active: true, SubField: &s}, {User: &User{Username: "jane_doe", Email: "jane@example.com"}, Role: "User", Active: false}, } group := Group{Name: "Admins", Members: members} @@ -108,20 +125,24 @@ func TestFlattenStructWithArrayOfPointersInGroup(t *testing.T) { }) expectedMap := map[string]interface{}{ - "Name": "Admins", - "Members.0.User.Username": "john_doe", - "Members.0.User.Email": "john@example.com", - "Members.0.Role": "Admin", - "Members.0.Active": true, - "Members.1.User.Username": "jane_doe", - "Members.1.User.Email": "jane@example.com", - "Members.1.Role": "User", - "Members.1.Active": false, + "Name": "Admins", + "Members.0.User.Username": "john_doe", + "Members.0.User.Email": "john@example.com", + "Members.0.Role": "Admin", + "Members.0.Active": true, + "Members.1.User.Username": "jane_doe", + "Members.1.User.Email": "jane@example.com", + "Members.1.Role": "User", + "Members.1.Active": false, + "Members.0.SubField.0.PolicyName": "policy-s3-operator", + "Members.0.SubField.0.Statement.0.Effect": "Allow", + "Members.0.SubField.0.Statement.0.Action.0": "s3:PutObject", + "Members.0.SubField.0.Statement.0.Action.1": "s3:GetObject", + "Members.0.SubField.0.Statement.0.Resource.0": "arn:aws:s3:::personal-s3-bucket/*", } if !reflect.DeepEqual(flattenedMap, expectedMap) { - fmt.Println(flattenedMap) - fmt.Println(expectedMap) + fmt.Println(diff.Diff(flattenedMap, expectedMap)) t.Errorf("Flattened result does not match the expected map. Got: %+v, Expected: %+v", flattenedMap, expectedMap) } }