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.
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.
<!DOCTYPE html>
<html><body>404 page not found</body></html>{
"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 intsays the document is perfectly valid JSON and only the target type is wrong — the API sent"12"where your struct field is anint. There is nothing to fix in the JSON. Change the field type, or give it ajson.Numberor a customUnmarshalJSON. - Decoding into
interface{}never produces that type error and is a poor workaround — every number becomes afloat64, which quietly mangles large integer IDs.