{"record":{"id":"ce756c695eb45d6c","repo":"vitessio/vitess","slug":"invalid-number-at-position-d-in-json","errorCode":null,"errorMessage":"invalid number at position %d in JSON","messagePattern":"invalid number at position (.+?) in JSON","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/mysql/json/marshal.go","lineNumber":496,"sourceCode":"\t\t\tr |= rune(ch - '0')\n\t\tcase ch >= 'a' && ch <= 'f':\n\t\t\tr |= rune(ch - 'a' + 10)\n\t\tcase ch >= 'A' && ch <= 'F':\n\t\t\tr |= rune(ch - 'A' + 10)\n\t\tdefault:\n\t\t\treturn -1\n\t\t}\n\t}\n\treturn r\n}\n\nfunc (w *sqlWriter) writeNumber(top bool) error {\n\t// Use the parser's readFloat to validate number grammar, rejecting\n\t// malformed inputs like \"1+2\", \"1..2\", or \"1e+\" that a simple\n\t// character-class loop would accept.\n\tn, _, ok := readFloat(w.data[w.pos:])\n\tif !ok || n == 0 {\n\t\treturn fmt.Errorf(\"invalid number at position %d in JSON\", w.pos)\n\t}\n\tif top {\n\t\tw.buf.WriteString(\"CAST(\")\n\t}\n\tw.buf.Write(w.data[w.pos : w.pos+n])\n\tw.pos += n\n\tif top {\n\t\tw.buf.WriteString(\" as JSON)\")\n\t}\n\treturn nil\n}\n\nfunc (w *sqlWriter) writeBool(top bool) error {\n\tif top {\n\t\tw.buf.WriteString(\"CAST(_utf8mb4'\")\n\t}\n\tif w.pos+4 <= len(w.data) && string(w.data[w.pos:w.pos+4]) == \"true\" {\n\t\tw.buf.WriteString(\"true\")","sourceCodeStart":478,"sourceCodeEnd":514,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/mysql/json/marshal.go#L478-L514","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Validate the whole document with encoding/json (json.Valid/Unmarshal) before writing; the standard parser rejects these number forms with position information.","Fix the number at the reported position to a valid JSON literal (remove the stray + or extra ., supply exponent digits, e.g. 1e10).","Replace string formatting of numbers in the producer with json.Marshal of typed numeric values so Go's formatter emits valid literals.","Check for data corruption/truncation upstream if numbers in otherwise valid documents are being mangled."],"exampleFix":"// before: formatting artifacts produce invalid tokens\ns := fmt.Sprintf(`{\"v\": %s+}`, numStr) // e.g. 1e+\n// after: marshal typed values\nb, _ := json.Marshal(map[string]float64{\"v\": v})","handlingStrategy":"validation","validationCode":"if !json.Valid(input) {\n\treturn fmt.Errorf(\"invalid JSON number in document\")\n}","typeGuard":null,"tryCatchPattern":"if err := writeJSONAsSQL(input); err != nil {\n\tif strings.Contains(err.Error(), \"invalid number at position\") {\n\t\tvar se *json.SyntaxError\n\t\tif e := json.Unmarshal(raw, new(any)); e != nil && errors.As(e, &se) {\n\t\t\treturn fmt.Errorf(\"bad number near offset %d\", se.Offset)\n\t\t}\n\t\treturn err\n\t}\n\treturn err\n}","preventionTips":["Emit numbers as typed values through json.Marshal, not formatted strings","Avoid %s-formatting numeric strings that may carry + or trailing signs","Validate documents at the ingestion boundary with json.Valid","Reject inputs with arithmetic-like tokens (1+2, 1..2) before processing"],"tags":["json","numbers","parsing","mysql"],"backgroundTag":"invalid-json-number","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}