'<' is an invalid start of a value

ErrorC#

System.Text.Json.JsonException: '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.

Where JSON.parse stops

<!DOCTYPE html>
<html><body>502 Bad Gateway</body></html>

What it means

System.Text.Json stopped on the first character it read, because no JSON value can start with it. The character in quotes is that one. A < means an HTML page arrived where JSON was expected. The fix is in the response, not in your class.

Last verified on System.Text.Json 10.0; Newtonsoft.Json 13.0.

The validator opens with the error marked. Examples travel in the URL fragment, which browsers never send to a server.

Express Lint lays broken JSON out by bracket depth and marks the line where it stops. It changes only spaces and line breaks outside strings. Strings in either quote stay as written. If the quotes do not pair up, it leaves your text as it is. Ctrl+Z undoes it. See how it works.

Usually
'<': the body is an HTML page'e' or another letter: the body is plain text'u': undefined from JavaScriptReading Path, LineNumber and BytePositionInLineIt is strict: a trailing comma, single quotes, an empty bodyThe text stops early
Fix
Log the raw body and the status code before you deserialize.

The fix

Log the raw body and the status code before you deserialize. If the body is an HTML page, fix the request or the proxy, not the class. The two samples here are the same failed call: what arrived, and what the API should have sent.

broken.jsonFails to parse
<!DOCTYPE html>
<html><body>502 Bad Gateway</body></html>
JSON.parse stops at line 1, column 1.

Lines 1, 2, 3 and 4 change.

fixed.jsonParses
{
  "error": "bad gateway",
  "status": 502
}
JSON.parse reads it.

How the message appears

The first line is the example on this page. The rest are the same message for other bodies: plain text, the word undefined, and the faults System.Text.Json rejects that other parsers let through, which are a comma after the last member, single quotes, an empty body, a missing comma and a string that stops early.

Console
  • System.Text.Json.JsonException: '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
  • System.Text.Json.JsonException: 'e' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
  • System.Text.Json.JsonException: 'u' is an invalid start of a value. Path: $.status | LineNumber: 0 | BytePositionInLine: 20.
  • System.Text.Json.JsonException: The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. Path: $ | LineNumber: 0 | BytePositionInLine: 30.
  • System.Text.Json.JsonException: ''' is an invalid start of a property name. Expected a '"'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
  • System.Text.Json.JsonException: The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
  • System.Text.Json.JsonException: '"' is invalid after a value. Expected either ',', '}', or ']'. Path: $ | LineNumber: 2 | BytePositionInLine: 2.
  • System.Text.Json.JsonException: Expected end of string, but instead reached end of data. Path: $.status | LineNumber: 0 | BytePositionInLine: 25.

What causes it

  1. '<': the body is an HTML page

    A 502 from a proxy, a 404 page, a login redirect or a WAF challenge usually comes back as HTML. Your code asked for JSON and read the page. Path: $ and LineNumber: 0 | BytePositionInLine: 0 say it failed on the very first character, before it had read anything. Nothing in your class can fix that. Log the body and the status code.

  2. 'e' or another letter: the body is plain text

    A text error such as error: upstream timeout starts with e, so the message says 'e' is an invalid start of a value. The letter in quotes is the first letter of the text. Log the body and read it.

  3. 'u': undefined from JavaScript

    A client that builds JSON by joining strings writes undefined when a variable has no value. The message says 'u', and Path names the field, $.status in this example. BytePositionInLine: 20 is the 21st character on the line, because the count starts at 0. See undefined is not valid JSON for the JavaScript side.

  4. Reading Path, LineNumber and BytePositionInLine

    Path says where in the document it stopped. $ is the top, and $.status is the status field. LineNumber and BytePositionInLine both start at 0, so LineNumber: 0 is the first line. Take a document with no comma after "id": 7. The message says LineNumber: 2 | BytePositionInLine: 2, which is the quote that starts "status" on the third line. The comma belongs at the end of the line above it.

  5. It is strict: a trailing comma, single quotes, an empty body

    A comma after the last member gives The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. Single quotes give ''' is an invalid start of a property name. Expected a '"'. An empty body gives The input does not contain any JSON tokens. Newtonsoft reads the first two with no error, so a payload that worked before you switched libraries can fail now. See the Newtonsoft page. The message points at the reader options. JsonSerializerOptions.AllowTrailingCommas is the one for a trailing comma, and ReadCommentHandling is the one for comments. Not tested here. The better fix is to get the sender to write valid JSON.

  6. The text stops early

    A body cut off inside a string says Expected end of string, but instead reached end of data. Path names the field that was open, $.status here. Look for a connection that dropped or a buffer that was cut.

Worth knowing

NoteThe same failure in other languages: JavaScript says Unexpected token '<', "<!DOCTYPE "... is not valid JSON (the JavaScript page) and Go says invalid character '<' looking for beginning of value (the Go page). Newtonsoft words it as Unexpected character encountered while parsing value.

NoteThe messages here come from JsonSerializer.Deserialize<Order> into a small class with an Id and a Status. Other entry points, such as JsonDocument.Parse, were not run.

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.