jsonType misclassifies json.Number as a JSON string, so UseNumber-decoded instances fail numeric type validation
- Dominant language
- Go
- Stars
- 478
- Forks
- 37
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
`jsonType` classifies a value by `reflect.Kind`. A `json.Number`'s kind is `String`, so any instance decoded with `json.Decoder.UseNumber()` is reported as a JSON string and fails the `type` keyword for `"integer"` and `"number"`.
This is inconsistent with the rest of the package: `jsonNumber` (`jsonschema/util.go`) explicitly converts a `json.Number` to a `big.Rat`, so `minimum`, `maximum`, `multipleOf` and `enum` all handle `json.Number` correctly. Only `type` does not, and because `type` usually runs first, the others rarely get the chance.
### Reproducer
```go
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/google/jsonschema-go/jsonschema"
)
func main() {
schema := &jsonschema.Schema{Type: "integer"}
resolved, err := schema.Resolve(nil)
if err != nil {
panic(err)
}
data := []byte(`42`)
var plain any
json.Unmarshal(data, &plain)
fmt.Printf("plain %T -> %v\n", plain, resolved.Validate(plain))
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
var num any
dec.Decode(&num)
fmt.Printf("number %T -> %v\n", num, resolved.Validate(num))
}
```
With `github.com/google/jsonschema-go v0.4.3` (the `jsonType` body is unchanged on `main` at `794ce5e`):
```
plain float64 ->
number json.Number -> validating root: type: 42 has type "string", want "integer"
```
### Why it matters
`UseNumber()` is the standard way to keep a JSON number's exact literal text through a decode/encode round-trip. Decoding into `map[string]any` otherwise represents every number as a `float64`, which silently rounds integers outside the IEEE-754 safe range — `9007199254740993` becomes `9007199254740992`.
Anyone who reaches for `UseNumber()` to avoid that, and then validates the result, hits this instead. That is exactly what happened in the Go MCP SDK: [modelcontextprotocol/go-sdk#1201](https://github.com/modelcontextprotocol/go-sdk/issues/1201) is a precision bug whose natural fix is `UseNumber()`, and the workaround I ended up sending ([go-sdk#1244](https://github.com/modelcontextprotocol/go-sdk/pull/1244)) has to validate a separate `float64`-converted copy purely to route around this.
### Suggested fix
Recognize `json.Number` in `jsonType` before the `reflect.Kind` switch, reusing `jsonNumber` so the integer/number split matches the existing `float64` behaviour (a whole number is `"integer"`):
```go
if v.IsValid() && v.Type() == reflect.TypeFor[json.Number]() {
r, ok := jsonNumber(v)
if !ok {
return "", false
}
if r.IsInt() {
return "integer", true
}
return "number", true
}
```
Happy to send a PR with that plus tests if you'd like it in this shape.
Contributor guide
Research direction
Start in jsonschema/util.go and trace the jsonType entry point alongside the existing jsonNumber handling. Run the UseNumber reproducer and add tests for json.Number values representing integers and non-integer numbers. Done means UseNumber-decoded values pass the appropriate integer or number type validation, while invalid values remain rejected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100