diff --git a/script.go b/script.go index 1fd40bc..d7d1bc3 100644 --- a/script.go +++ b/script.go @@ -712,38 +712,50 @@ func (p *Pipe) Join() *Pipe { }) } -// JQ executes query on the pipe's contents (presumed to be JSON), producing -// the result. An invalid query will set the appropriate error on the pipe. +// JQ executes query on the pipe's contents (presumed to be valid JSON or +// [JSONLines] data), applying the query to each newline-delimited input value +// and producing results until the first error is encountered. An invalid query +// or value will set the appropriate error on the pipe. // // The exact dialect of JQ supported is that provided by // [github.com/itchyny/gojq], whose documentation explains the differences // between it and standard JQ. +// +// [JSONLines]: https://jsonlines.org/ func (p *Pipe) JQ(query string) *Pipe { + parsedQuery, err := gojq.Parse(query) + if err != nil { + return p.WithError(err) + } + code, err := gojq.Compile(parsedQuery) + if err != nil { + return p.WithError(err) + } return p.Filter(func(r io.Reader, w io.Writer) error { - q, err := gojq.Parse(query) - if err != nil { - return err - } - var input interface{} - err = json.NewDecoder(r).Decode(&input) - if err != nil { - return err - } - iter := q.Run(input) - for { - v, ok := iter.Next() - if !ok { - return nil - } - if err, ok := v.(error); ok { - return err - } - result, err := gojq.Marshal(v) + dec := json.NewDecoder(r) + for dec.More() { + var input any + err := dec.Decode(&input) if err != nil { return err } - fmt.Fprintln(w, string(result)) + iter := code.Run(input) + for { + v, ok := iter.Next() + if !ok { + break + } + if err, ok := v.(error); ok { + return err + } + result, err := gojq.Marshal(v) + if err != nil { + return err + } + fmt.Fprintln(w, string(result)) + } } + return nil }) } diff --git a/script_test.go b/script_test.go index a3f9124..12f4d90 100644 --- a/script_test.go +++ b/script_test.go @@ -743,7 +743,6 @@ func TestJQWithDotQueryPrettyPrintsInput(t *testing.T) { t.Fatal(err) } if want != got { - t.Error(want, got) t.Error(cmp.Diff(want, got)) } } @@ -757,7 +756,6 @@ func TestJQWithFieldQueryProducesSelectedField(t *testing.T) { t.Fatal(err) } if want != got { - t.Error(want, got) t.Error(cmp.Diff(want, got)) } } @@ -771,7 +769,6 @@ func TestJQWithArrayQueryProducesRequiredArray(t *testing.T) { t.Fatal(err) } if want != got { - t.Error(want, got) t.Error(cmp.Diff(want, got)) } } @@ -785,7 +782,6 @@ func TestJQWithArrayInputAndElementQueryProducesSelectedElement(t *testing.T) { t.Fatal(err) } if want != got { - t.Error(want, got) t.Error(cmp.Diff(want, got)) } } @@ -799,7 +795,32 @@ func TestJQHandlesGithubJSONWithRealWorldExampleQuery(t *testing.T) { t.Fatal(err) } if want != got { - t.Error(want, got) + t.Error(cmp.Diff(want, got)) + } +} + +func TestJQCorrectlyQueriesMultilineInputFields(t *testing.T) { + t.Parallel() + input := `{"a":1}` + "\n" + `{"a":2}` + want := "1\n2\n" + got, err := script.Echo(input).JQ(".a").String() + if err != nil { + t.Fatal(err) + } + if want != got { + t.Error(cmp.Diff(want, got)) + } +} + +func TestJQCorrectlyQueriesMultilineInputArrays(t *testing.T) { + t.Parallel() + input := `[1, 2, 3]` + "\n" + `[4, 5, 6]` + want := "1\n4\n" + got, err := script.Echo(input).JQ(".[0]").String() + if err != nil { + t.Fatal(err) + } + if want != got { t.Error(cmp.Diff(want, got)) } } @@ -813,6 +834,28 @@ func TestJQErrorsWithInvalidQuery(t *testing.T) { } } +func TestJQErrorsWithInvalidInput(t *testing.T) { + t.Parallel() + input := "invalid JSON value" + _, err := script.Echo(input).JQ(".").String() + if err == nil { + t.Error("want error from invalid JSON input, got nil") + } +} + +func TestJQProducesValidResultsUntilFirstError(t *testing.T) { + t.Parallel() + input := "[1]\ninvalid JSON value\n[2]" + want := "1\n" + got, err := script.Echo(input).JQ(".[0]").String() + if err == nil { + t.Error("want error from invalid JSON input, got nil") + } + if want != got { + t.Error(cmp.Diff(want, got)) + } +} + func TestLastDropsAllButLastNLinesOfInput(t *testing.T) { t.Parallel() input := "a\nb\nc\n"