Skip to content

Commit

Permalink
fix: handle nested objects when skipping unused fields
Browse files Browse the repository at this point in the history
Signed-off-by: Kyle McCullough <kylemcc@gmail.com>
  • Loading branch information
kylemcc committed Mar 28, 2023
1 parent 8de3b25 commit a314f23
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 3 deletions.
13 changes: 10 additions & 3 deletions flatten_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (fj *flattenJSON) readObject(pathNode SegmentsTreeTracker) error {
if pathNode.IsRoot() {
return errEarlyStop
} else {
return fj.skipUntil('}')
return fj.leaveObject()
}
}

Expand Down Expand Up @@ -532,7 +532,7 @@ func (fj *flattenJSON) readLiteral(literal []byte) ([]byte, error) {
return literal, nil
}

func (fj *flattenJSON) skipUntil(symbol byte) error {
func (fj *flattenJSON) leaveObject() error {
for fj.eventIndex < len(fj.event) {
ch := fj.event[fj.eventIndex]

Expand All @@ -542,7 +542,14 @@ func (fj *flattenJSON) skipUntil(symbol byte) error {
if err != nil {
return err
}
case symbol:
case '{', '[':
// ch+2 is the matching closing brace, since both '}' and ']' are 2 characters away
// from '{' and ']', respectively
err := fj.skipBlock(ch, ch+2)
if err != nil {
return err
}
case '}':
return nil
}

Expand Down
71 changes: 71 additions & 0 deletions flatten_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package quamina

import (
"bytes"
"fmt"
"os"
"testing"
)
Expand Down Expand Up @@ -315,6 +316,76 @@ func TestFJErrorCases(t *testing.T) {
}
}

func TestSkipUnusedPaths(t *testing.T) {
// Each of theses cases has a nested object with additional values that should
// be skipped after the specified paths have been checked
//
// e.g., take the following object and paths:
//
// object: { "a": { "b": 1, "c": 2} }
// paths: ["a\nb", "d"]
//
// After the flattener evaluates a.b, it should skip a.c before looking
// for d in the outer object.
//
// These tests make sure that the flattener correctly skips remaining
// values including nested objects and arrays.
//
// The tests below contain an additional path to look for after the
// paths in the nested object to make sure the flattener correctly
// exits the nested object and begings parsing the rest of the event
// at the correct location.
cases := []struct {
event string
matcherPaths []string
}{
{
event: `{"nested":{"thing":"whatever","extra":{}}}`,
matcherPaths: []string{"nested\nthing", "another"},
},
{
event: `{"nested":{"thing":"whatever","extra":{"empty": false}}}`,
matcherPaths: []string{"nested\nthing", "another"},
},
{
event: `{"nested":{"thing":"whatever","extra":[{}]}}`,
matcherPaths: []string{"nested\nthing", "another"},
},
{
event: `{"nested":{"thing":"whatever","extra":[{"empty": false}]}}`,
matcherPaths: []string{"nested\nthing", "another"},
},
{
event: `{"nested":{"thing":"whatever","extra":[]}}`,
matcherPaths: []string{"nested\nthing", "another"},
},
{
event: `{"nested":{"thing":"whatever","extra":[1,"two",true,null]}}`,
matcherPaths: []string{"nested\nthing", "another"},
},
{
event: `{"nested":{"thing":"whatever","extra":[],"andAnother":{}}}`,
matcherPaths: []string{"nested\nthing", "another"},
},
}

for i, c := range cases {
t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) {
matcher := fakeMatcher(c.matcherPaths...)

fj := newJSONFlattener().(*flattenJSON)

// ignore the fields - this test isn't concerned with the flattening result
_, err := fj.Flatten([]byte(c.event), matcher.getSegmentsTreeTracker())

// make sure the flattener didn't return an error
if err != nil {
t.Fatalf("failed to flatten json: %v", err)
}
})
}
}

func fakeMatcher(paths ...string) *coreMatcher {
m := newCoreMatcher()
for _, path := range paths {
Expand Down

0 comments on commit a314f23

Please sign in to comment.