Skip to content

Commit d627bb9

Browse files
author
James Haggerty
committed
Add line number to JSON parse error
1 parent e287c64 commit d627bb9

File tree

2 files changed

+73
-4
lines changed

2 files changed

+73
-4
lines changed

jp.go

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package main
33
import (
44
"encoding/json"
55
"fmt"
6+
"io"
67
"io/ioutil"
78
"os"
9+
"sort"
810

911
"github.com/jmespath/jp/Godeps/_workspace/src/github.com/codegangsta/cli"
1012
"github.com/jmespath/jp/Godeps/_workspace/src/github.com/jmespath/go-jmespath"
@@ -83,19 +85,28 @@ func runMain(c *cli.Context) int {
8385
return 0
8486
}
8587
var input interface{}
86-
var jsonParser *json.Decoder
88+
var inputStream io.Reader
8789
if c.String("filename") != "" {
8890
f, err := os.Open(c.String("filename"))
8991
if err != nil {
9092
return errMsg("Error opening input file: %s", err)
9193
}
92-
jsonParser = json.NewDecoder(f)
94+
inputStream = f
9395

9496
} else {
95-
jsonParser = json.NewDecoder(os.Stdin)
97+
inputStream = os.Stdin
9698
}
99+
newlineNumberReader := NewLineNumberReader(inputStream)
100+
jsonParser := json.NewDecoder(newlineNumberReader)
97101
if err := jsonParser.Decode(&input); err != nil {
98-
errMsg("Error parsing input json: %s\n", err)
102+
syntaxError, ok := err.(*json.SyntaxError)
103+
if ok && syntaxError.Offset == int64(int(syntaxError.Offset)) {
104+
line, char := newlineNumberReader.ConvertOffset(int(syntaxError.Offset))
105+
errMsg("Error parsing input json: %s (line: %d, char: %d)\n",
106+
syntaxError, line, char)
107+
} else {
108+
errMsg("Error parsing input json: %s", err)
109+
}
99110
return 2
100111
}
101112
result, err := jmespath.Search(expression, input)
@@ -121,3 +132,47 @@ func runMain(c *cli.Context) int {
121132
os.Stdout.WriteString("\n")
122133
return 0
123134
}
135+
136+
type LineNumberReader struct {
137+
actualReader io.Reader
138+
newlinePositions []int
139+
bytesRead int
140+
}
141+
142+
func NewLineNumberReader(actualReader io.Reader) *LineNumberReader {
143+
return &LineNumberReader{
144+
actualReader: actualReader,
145+
}
146+
}
147+
148+
func (lnr *LineNumberReader) Read(p []byte) (n int, err error) {
149+
n, err = lnr.actualReader.Read(p)
150+
151+
if err != nil || n == 0 {
152+
return
153+
}
154+
155+
for i, v := range p {
156+
if i >= n {
157+
return
158+
}
159+
160+
if v == '\n' {
161+
// add 1 so we record the position of the first character, not the '\n'
162+
lnr.newlinePositions = append(lnr.newlinePositions, lnr.bytesRead+i+1)
163+
}
164+
}
165+
166+
lnr.bytesRead = lnr.bytesRead + n
167+
return
168+
}
169+
170+
func (lnr *LineNumberReader) ConvertOffset(offset int) (linePos int, charPos int) {
171+
index := sort.SearchInts(lnr.newlinePositions, offset)
172+
// Humans are 1 indexed...
173+
if index == 0 {
174+
return 1, offset
175+
} else {
176+
return index + 1, offset - lnr.newlinePositions[index-1]
177+
}
178+
}

test/cases/search.bats

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,17 @@ ASTField {
6969
[ "$status" -eq 0 ]
7070
[ "$output" == "12345" ]
7171
}
72+
73+
@test "Report error in JSON with no newlines" {
74+
echo -n '{"foo": bar}' > "$BATS_TMPDIR/input.json"
75+
run ./jp -f "$BATS_TMPDIR/input.json" '@'
76+
[ "$status" -eq 2 ]
77+
[ "$output" == "Error parsing input json: invalid character 'b' looking for beginning of value (line: 1, char: 9)" ]
78+
}
79+
80+
@test "Report error in JSON with newlines" {
81+
echo -en '{"foo": \nbar}' > "$BATS_TMPDIR/input.json"
82+
run ./jp -f "$BATS_TMPDIR/input.json" '@'
83+
[ "$status" -eq 2 ]
84+
[ "$output" == "Error parsing input json: invalid character 'b' looking for beginning of value (line: 2, char: 1)" ]
85+
}

0 commit comments

Comments
 (0)