invalid character '<' looking for beginning of value

Go tells you two things: the byte it could not use, and what it was in the middle of reading. "looking for beginning of value" means it was at the start of a value, so the byte quoted is the very first character of something that was supposed to be JSON — which is why < is the one people see most.

How the message appears

Go wording, from encoding/json. The error is a *json.SyntaxError and carries a byte Offset field that the string form does not show.

  • invalid character '<' looking for beginning of value
  • invalid character '}' looking for beginning of object key string
  • invalid character 'o' in literal null (expecting 'u')
  • unexpected end of JSON input

What causes it

The byte is '<' — you are unmarshalling HTML

A 404 page, a login redirect, a proxy or load-balancer error, or a WAF challenge. This is by far the most common form of the message, and no change to your struct or your JSON will fix it, because there is no JSON involved.

'}' looking for beginning of object key string

A trailing comma. The parser consumed the comma, went looking for the next key, and found the closing brace instead.

'o' in literal null, and other half-read keywords

A keyword that is not quite a keyword: None from Python, NULL in capitals, or a null cut in half by a truncated response. Go reports which letter it expected next, which tells you it was mid-keyword rather than mid-value.

An empty body

Reported separately as unexpected end of JSON input. Usually a 204, a HEAD request, or a body that was already read once — an http.Response.Body can only be consumed a single time.

The fix

Check the status code and content type before you unmarshal, and log the raw body when they are not what you expect. The two samples here are the same failed request: what actually arrived, and what the API should have returned — the fix is upstream, not in the parsing code.

Fails to parse
<!DOCTYPE html>
<html><body>404 page not found</body></html>
Parses
{
  "error": "not found",
  "status": 404
}

The example travels in the URL fragment, which browsers never send to a server.

Worth knowing

  • The offset is available programmatically: var se *json.SyntaxError; if errors.As(err, &se) { log.Print(se.Offset) } gives the byte position, which beats guessing from the character alone.
  • A different family of Go errors means the opposite of this one: json: cannot unmarshal string into Go value of type int says the document is perfectly valid JSON and only the target type is wrong — the API sent "12" where your struct field is an int. There is nothing to fix in the JSON. Change the field type, or give it a json.Number or a custom UnmarshalJSON.
  • Decoding into interface{} never produces that type error and is a poor workaround — every number becomes a float64, which quietly mangles large integer IDs.

Your JSON and text are processed entirely in your browser and are never sent anywhere. To understand how the site is used we record each page view — the page, the referrer and an approximate location from your IP address — and keep it for 90 days. Nothing is stored on your device unless you choose it. Accept to also store a device identifier here and record your browser and device characteristics, which is what lets a return visit be told from a new one. Decline and nothing is sent at all. We respect Do Not Track. See our privacy policy.