Skip to content

Commit

Permalink
Add line number to JSON parse error
Browse files Browse the repository at this point in the history
  • Loading branch information
James Haggerty committed Aug 9, 2017
1 parent e287c64 commit d627bb9
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 4 deletions.
63 changes: 59 additions & 4 deletions jp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"sort"

"github.com/jmespath/jp/Godeps/_workspace/src/github.com/codegangsta/cli"
"github.com/jmespath/jp/Godeps/_workspace/src/github.com/jmespath/go-jmespath"
Expand Down Expand Up @@ -83,19 +85,28 @@ func runMain(c *cli.Context) int {
return 0
}
var input interface{}
var jsonParser *json.Decoder
var inputStream io.Reader
if c.String("filename") != "" {
f, err := os.Open(c.String("filename"))
if err != nil {
return errMsg("Error opening input file: %s", err)
}
jsonParser = json.NewDecoder(f)
inputStream = f

} else {
jsonParser = json.NewDecoder(os.Stdin)
inputStream = os.Stdin
}
newlineNumberReader := NewLineNumberReader(inputStream)
jsonParser := json.NewDecoder(newlineNumberReader)
if err := jsonParser.Decode(&input); err != nil {
errMsg("Error parsing input json: %s\n", err)
syntaxError, ok := err.(*json.SyntaxError)
if ok && syntaxError.Offset == int64(int(syntaxError.Offset)) {
line, char := newlineNumberReader.ConvertOffset(int(syntaxError.Offset))
errMsg("Error parsing input json: %s (line: %d, char: %d)\n",
syntaxError, line, char)
} else {
errMsg("Error parsing input json: %s", err)
}
return 2
}
result, err := jmespath.Search(expression, input)
Expand All @@ -121,3 +132,47 @@ func runMain(c *cli.Context) int {
os.Stdout.WriteString("\n")
return 0
}

type LineNumberReader struct {
actualReader io.Reader
newlinePositions []int
bytesRead int
}

func NewLineNumberReader(actualReader io.Reader) *LineNumberReader {
return &LineNumberReader{
actualReader: actualReader,
}
}

func (lnr *LineNumberReader) Read(p []byte) (n int, err error) {
n, err = lnr.actualReader.Read(p)

if err != nil || n == 0 {
return
}

for i, v := range p {
if i >= n {
return
}

if v == '\n' {
// add 1 so we record the position of the first character, not the '\n'
lnr.newlinePositions = append(lnr.newlinePositions, lnr.bytesRead+i+1)
}
}

lnr.bytesRead = lnr.bytesRead + n
return
}

func (lnr *LineNumberReader) ConvertOffset(offset int) (linePos int, charPos int) {
index := sort.SearchInts(lnr.newlinePositions, offset)
// Humans are 1 indexed...
if index == 0 {
return 1, offset
} else {
return index + 1, offset - lnr.newlinePositions[index-1]
}
}
14 changes: 14 additions & 0 deletions test/cases/search.bats
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,17 @@ ASTField {
[ "$status" -eq 0 ]
[ "$output" == "12345" ]
}

@test "Report error in JSON with no newlines" {
echo -n '{"foo": bar}' > "$BATS_TMPDIR/input.json"
run ./jp -f "$BATS_TMPDIR/input.json" '@'
[ "$status" -eq 2 ]
[ "$output" == "Error parsing input json: invalid character 'b' looking for beginning of value (line: 1, char: 9)" ]
}

@test "Report error in JSON with newlines" {
echo -en '{"foo": \nbar}' > "$BATS_TMPDIR/input.json"
run ./jp -f "$BATS_TMPDIR/input.json" '@'
[ "$status" -eq 2 ]
[ "$output" == "Error parsing input json: invalid character 'b' looking for beginning of value (line: 2, char: 1)" ]
}

0 comments on commit d627bb9

Please sign in to comment.