google / google/jsonschema-go

jsonType misclassifies json.Number as a JSON string, so UseNumber-decoded instances fail numeric type validation

Open Beginner friendly
#80 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.