vitessio/vitess · error

invalid number at position %d in JSON

Error message

invalid number at position %d in JSON

What it means

writeNumber validates the numeric token starting at the current position using the SQL parser's readFloat, which enforces the JSON/SQL number grammar. If no valid number can be read (ok is false, or zero length was consumed), the writer reports the failure with the offset in the input document. This rejects malformed numbers like 1+2, 1..2, or 1e+ that a naive character-class check would accept, ensuring the generated CAST(...) expression contains only well-formed literals.

Source

Thrown at go/mysql/json/marshal.go:496

			r |= rune(ch - '0')
		case ch >= 'a' && ch <= 'f':
			r |= rune(ch - 'a' + 10)
		case ch >= 'A' && ch <= 'F':
			r |= rune(ch - 'A' + 10)
		default:
			return -1
		}
	}
	return r
}

func (w *sqlWriter) writeNumber(top bool) error {
	// Use the parser's readFloat to validate number grammar, rejecting
	// malformed inputs like "1+2", "1..2", or "1e+" that a simple
	// character-class loop would accept.
	n, _, ok := readFloat(w.data[w.pos:])
	if !ok || n == 0 {
		return fmt.Errorf("invalid number at position %d in JSON", w.pos)
	}
	if top {
		w.buf.WriteString("CAST(")
	}
	w.buf.Write(w.data[w.pos : w.pos+n])
	w.pos += n
	if top {
		w.buf.WriteString(" as JSON)")
	}
	return nil
}

func (w *sqlWriter) writeBool(top bool) error {
	if top {
		w.buf.WriteString("CAST(_utf8mb4'")
	}
	if w.pos+4 <= len(w.data) && string(w.data[w.pos:w.pos+4]) == "true" {
		w.buf.WriteString("true")

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Validate the whole document with encoding/json (json.Valid/Unmarshal) before writing; the standard parser rejects these number forms with position information.
  2. Fix the number at the reported position to a valid JSON literal (remove the stray + or extra ., supply exponent digits, e.g. 1e10).
  3. Replace string formatting of numbers in the producer with json.Marshal of typed numeric values so Go's formatter emits valid literals.
  4. Check for data corruption/truncation upstream if numbers in otherwise valid documents are being mangled.

Example fix

// before: formatting artifacts produce invalid tokens
s := fmt.Sprintf(`{"v": %s+}`, numStr) // e.g. 1e+
// after: marshal typed values
b, _ := json.Marshal(map[string]float64{"v": v})
Defensive patterns

Strategy: validation

Validate before calling

if !json.Valid(input) {
	return fmt.Errorf("invalid JSON number in document")
}

Try / catch

if err := writeJSONAsSQL(input); err != nil {
	if strings.Contains(err.Error(), "invalid number at position") {
		var se *json.SyntaxError
		if e := json.Unmarshal(raw, new(any)); e != nil && errors.As(e, &se) {
			return fmt.Errorf("bad number near offset %d", se.Offset)
		}
		return err
	}
	return err
}

Prevention

When it happens

Trigger: Passing a JSON document to the writer whose number token is malformed at w.pos: two decimal points (1..2), an exponent with no digits (1e+, 1e-), characters not part of a number (1+2 where a value was expected), a bare minus with no digits (-), or leading characters like +2 or 0x1F that JSON numbers do not allow.

Common situations: Custom code that formats numbers into JSON strings using fmt.Sprintf with wrong verbs producing artifacts (1e+ without exponent digits after trimming); log or CSV-to-JSON converters emitting raw arithmetic expressions; corrupted payloads where digits were dropped or replaced; hand-written fixtures with typos.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/ce756c695eb45d6c. Report an issue: GitHub.