tidwall / gjson

Get JSON values quickly - JSON parser for Go
MIT License
14.1k stars 846 forks source link

Bug in Parse #255

Open dim opened 2 years ago

dim commented 2 years ago

Something fundamental has changed since v1.11.0 with Parse when applied to invalid JSON: https://go.dev/play/p/kaRXMp3u_hB

With 1.10.2:

fmt.Printf("%#v\n", gjson.Parse("E.X"))
// => gjson.Result{Type:0, Raw:"", Str:"", Num:0, Index:0, Indexes:[]int(nil)}

fmt.Printf("%#v\n", gjson.Parse("I.X"))
// => gjson.Result{Type:0, Raw:"", Str:"", Num:0, Index:0, Indexes:[]int(nil)}

Since 1.11.0:

fmt.Printf("%#v\n", gjson.Parse("E.X"))
// => gjson.Result{Type:0, Raw:"", Str:"", Num:0, Index:0, Indexes:[]int(nil)}

fmt.Printf("%#v\n", gjson.Parse("I.X"))
// => gjson.Result{Type:2, Raw:"I.X", Str:"", Num:0, Index:0, Indexes:[]int(nil)}

The very latest 1.12.0 is affected too

tidwall commented 2 years ago

I suspect that this difference in behavior is due to a recent change that allows for reading of Inf and NaN from #242.

The Parse operation does a very quick peek at the data to determine its type. In this case it sees the 'I' character and assumes that it's an Inf.

You will the same result with the following invalid JSON:

fmt.Printf("%#v\n", Parse("N.X"))
fmt.Printf("%#v\n", Parse("0.X"))
fmt.Printf("%#v\n", Parse("-.X"))

From the README (https://github.com/tidwall/gjson#validate-json):

Validate JSON

The Get and Parse functions expects that the json is well-formed. Bad json will not panic, but it may return back unexpected results.

If you are consuming JSON from an unpredictable source then you may want to validate prior to using GJSON.

if !gjson.Valid(json) {
  return errors.New("invalid json")
}
value := gjson.Get(json, "name.last")

If you know that the incoming JSON document might be invalid then I recommend using the Valid function prior to Parse, like in the example above, or by adding the @valid modifier following the Parse like:

fmt.Printf("%#v\n", Parse("E.X").Get("@valid"))
// => gjson.Result{Type:0, Raw:"", Str:"", Num:0, Index:0, Indexes:[]int(nil)}

fmt.Printf("%#v\n", Parse("I.X").Get("@valid"))
// => gjson.Result{Type:0, Raw:"", Str:"", Num:0, Index:0, Indexes:[]int(nil)}

fmt.Printf("%#v\n", Parse("125").Get("@valid"))
// => gjson.Result{Type:0, Raw:"", Str:"", Num:0, Index:0, Indexes:[]int(nil)}