JSON troubleshooting
Go JSON: invalid character looking for beginning of value
Diagnose unexpected prefixes, plain-text errors and byte-order marks in Go JSON input. Check HTTP bodies and use SyntaxError.Offset to locate the bad byte.
What the error means
Go encoding/json found a byte that cannot start the value it was about to read. The quoted byte describes what actually arrived. A < often starts HTML, u can start the invalid literal undefined, and an unexpected leading byte can indicate a byte-order mark or the wrong encoding. The error can occur at the start of a document or at a nested value, so inspect SyntaxError.Offset before assuming byte one is wrong.
Exact messages by runtime
Messages depend on the input and runtime version. Node and Python rows were reproduced locally. Other rows cite official documentation or source. Rows marked as fragments or templates preserve the source wording and are not complete exception transcripts. Browsers and libraries may append location details.
| Runtime | Message | Input and evidence |
|---|---|---|
| Go / encoding/json | invalid character '<' looking for beginning of value | []byte("<html>")Official Go 1.26 scanner and quoteChar source |
| Go / encoding/json | invalid character 'u' looking for beginning of value | []byte("undefined")Official Go 1.26 scanner source |
| Go / encoding/json | invalid character 'e' looking for beginning of value | []byte("error: unavailable")Official Go 1.26 scanner source |
| Go / encoding/json | invalid character 'ï' looking for beginning of value | UTF-8 BOM bytes EF BB BF before {}Source-derived: scanner reports first byte EF, quoteChar converts that byte to a quoted character |
| Python | json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) | json.loads("undefined")Reproduced in Python 3.14.4 |
| JavaScript / V8 | SyntaxError: "undefined" is not valid JSON | JSON.parse("undefined")Reproduced in Node.js 24.21.0 |
Causes you can reproduce
The body is a plain-text service error
A service can send a status message instead of the expected JSON body. Inspect status and Content-Type before decoding. For a body starting with <, follow the HTML-response guide.
var value any
err := json.Unmarshal([]byte("error: unavailable"), &value)
fmt.Println(err)These fragments use encoding/json and fmt inside a Go function. A complete program appears below.
A UTF-8 byte-order mark precedes the document
A UTF-8 BOM is the byte sequence EF BB BF. JSON producers must not add a BOM to networked JSON. If a documented legacy file has one, remove precisely that prefix at the file boundary.
body := append([]byte{0xef, 0xbb, 0xbf}, []byte("{}")...)
var value any
err := json.Unmarshal(body, &value)
fmt.Println(err)These fragments use encoding/json and fmt inside a Go function. A complete program appears below.
A JavaScript-only literal appears as a nested value
undefined is not a JSON literal. Correct the source object or use null only when the application meaning is actually a missing value.
var value any
err := json.Unmarshal([]byte(`{"value":undefined}`), &value)
fmt.Println(err)These fragments use encoding/json and fmt inside a Go function. A complete program appears below.
How to fix it
- Inspect the status, media type and a short body preview for HTTP input. For a file, inspect the first bytes in hexadecimal.
- Use errors.As to read *json.SyntaxError.Offset. It counts bytes read and is not the same as a Unicode character index.
- Fix plain-text or HTML service errors at the source. For a known legacy file, remove only a confirmed UTF-8 BOM; decode other encodings deliberately before parsing.
- Use json.Marshal to produce JSON and json.Unmarshal to validate a single complete document. For a stream, use repeated Decoder.Decode calls.
Locate the byte and handle a confirmed file BOM
This complete example handles a known file boundary. Removing arbitrary invalid bytes can hide corrupted input, so do not use a general character filter.
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
)
func main() {
body := append([]byte{0xef, 0xbb, 0xbf}, []byte("{}")...)
body = bytes.TrimPrefix(body, []byte{0xef, 0xbb, 0xbf})
var value any
if err := json.Unmarshal(body, &value); err != nil {
var syntax *json.SyntaxError
if errors.As(err, &syntax) {
fmt.Printf("JSON error after byte %d: %v\n", syntax.Offset, err)
}
return
}
fmt.Println(value)
}Inspect a fetch response before parsing
Fetch does not reject for HTTP error statuses. Read the body once as text, inspect it during local debugging, then check status and media type. The 204 branch below returns null as an application choice; adapt it to your API contract. Remove debugging output after diagnosis and redact sensitive values before sharing logs.
async function fetchJson(url) {
const res = await fetch(url, {
headers: { Accept: 'application/json' },
signal: AbortSignal.timeout(15000),
});
const text = await res.text(); // read the body only once
console.debug(res.status, res.url, res.headers.get('content-type'));
console.debug(text.slice(0, 200)); // local debugging only
if (!res.ok) throw new Error('HTTP ' + res.status);
if (res.status === 204) return null; // only for a documented no-body result
const type = (res.headers.get('content-type') || '')
.split(';')[0].trim().toLowerCase();
if (type !== 'application/json' && !/^application\/[^/]+\+json$/.test(type)) {
throw new Error('Expected JSON, received ' + (type || 'no Content-Type'));
}
if (!text.trim()) throw new Error('Expected JSON, received an empty body');
return JSON.parse(text);
}Inspect a Python requests response
Print r.status_code, r.headers and a preview of r.text before decoding. The Requests documentation explains why an empty response or invalid JSON can fail decoding even when the HTTP request itself succeeds.
import json
import requests
def fetch_json(url):
r = requests.get(url, headers={"Accept": "application/json"}, timeout=15)
print(r.status_code, r.url, r.headers.get("Content-Type"))
print(repr(r.text[:200])) # local debugging only
r.raise_for_status()
if r.status_code == 204:
return None # only for a documented no-body result
media_type = r.headers.get("Content-Type", "").split(";")[0].strip().lower()
is_json = media_type == "application/json" or (
media_type.startswith("application/") and media_type.endswith("+json")
)
if not is_json:
raise ValueError(f"Expected JSON, received {media_type or 'no Content-Type'}")
if not r.text.strip():
raise ValueError("Expected JSON, received an empty body")
return json.loads(r.text)Check the Go response before unmarshalling
Read and close the body, check status and media type, then use json.Unmarshal for one complete document. A SyntaxError.Offset counts bytes read. For a stream of records, use the multiple-values workflow instead.
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"mime"
"net/http"
"strings"
"time"
)
func fetchJSON(url string) (any, error) {
client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Get(url)
if err != nil { return nil, err }
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil { return nil, err }
preview := body
if len(preview) > 200 { preview = preview[:200] }
fmt.Printf("status=%d type=%q body=%q\n", resp.StatusCode,
resp.Header.Get("Content-Type"), preview) // local debugging only
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
if resp.StatusCode == http.StatusNoContent { return nil, nil }
mediaType, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil { return nil, fmt.Errorf("invalid Content-Type: %w", err) }
if mediaType != "application/json" &&
!(strings.HasPrefix(mediaType, "application/") && strings.HasSuffix(mediaType, "+json")) {
return nil, fmt.Errorf("expected JSON, received %s", mediaType)
}
var value any
if err := json.Unmarshal(body, &value); err != nil {
var syntax *json.SyntaxError
if errors.As(err, &syntax) {
return nil, fmt.Errorf("JSON error after byte %d: %w", syntax.Offset, err)
}
return nil, err
}
return value, nil
}
func main() {
value, err := fetchJSON("https://example.com/api/data")
if err != nil { fmt.Println(err); return }
fmt.Println(value)
}Find the exact position with Tool Kumar
- Copy the exact input text, including its original whitespace. For HTTP failures, copy the raw response body.
- Open the Tool Kumar JSON validator and paste the text. The document stays in your browser.
- Read the line and column in the error or fixable-issue banner. For an invalid document, select Go to error to move the caret to that location.
- Inspect the preceding token as well as the marked character. A parser marks where it discovered the problem; the missing quote, separator or value can start earlier.
Python character offsets are zero-based, while its displayed lines and columns are one-based. V8 string positions count UTF-16 code units; Go offsets count bytes read. Unicode text can make these offsets differ. Use the original text and its line/column context rather than copying an offset between runtimes.
Tool Kumar can classify some input as fixable loose JSON. Review a proposed change before using the repair action, then validate the result against your API contract. For empty input, inspect the producer or response because there is no character to select.
Frequently asked questions
What does invalid character < mean in Go?
The parser encountered < where a JSON value was required. If the body starts with HTML, inspect the endpoint, redirects and HTTP status. Do not remove the tag and try to parse the page.
Does this always mean the first byte is invalid?
No. It can occur wherever a value is expected, including after an object colon or array comma. Read SyntaxError.Offset to locate the byte.
Why can a UTF-8 BOM appear as ï in the error?
Go scans bytes. Its error formatter converts the first BOM byte, hexadecimal EF, to a quoted character. Inspect the raw bytes to confirm the full EF BB BF prefix.
Does a successful Decode validate the whole response?
One Decode reads one value. To require a single complete document, use json.Unmarshal on the full body or check that a second Decode returns io.EOF.
Related errors and tools
- Unexpected token '<' / DOCTYPE is not valid JSON
- Unexpected end of JSON input: empty or truncated data
- JSONDecodeError: Extra data and multiple JSON values
- "undefined" or "[object Object]" is not valid JSON
- All JSON error guides
- JSON syntax reference
- Generate Go structs from valid JSON
Standards and diagnostic sources
RFC 8259 and ECMA-404 define JSON syntax. Diagnostic wording belongs to the parser. The table links each cited message to its source.