JSON troubleshooting
Unexpected end of JSON input: empty or truncated data
Find and fix empty responses and truncated JSON behind Unexpected end of JSON input, Python Expecting value at char 0 and Go EOF errors.
What the error means
The parser reached the end before it could read a complete JSON value. An empty string has no value at all. A truncated object, array or string starts a value but never finishes it. Python also reports Expecting value at char 0 for HTML and other invalid prefixes, so that message alone does not prove the input is empty.
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 |
|---|---|---|
| JavaScript / V8 | SyntaxError: Unexpected end of JSON input | JSON.parse("") or JSON.parse('{"id":')Reproduced in Node.js 24.21.0 |
| Python, empty input | json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) | json.loads("")Reproduced in Python 3.14.4 |
| Python, unfinished value | json.decoder.JSONDecodeError: Expecting value: line 1 column 7 (char 6) | json.loads('{"id":')Reproduced in Python 3.14.4 |
| Go / json.Unmarshal | unexpected end of JSON input | Empty or incomplete JSON bytesOfficial Go 1.26 scanner source |
| Go / Decoder.Decode | EOF | No next value in an empty readerio.EOF return in the official Go source; incomplete tokens can produce unexpected EOF |
| Firefox | SyntaxError: JSON.parse: unexpected end of data | Incomplete JSON inputMDN diagnostic wording, location suffix omitted |
| Safari / JavaScriptCore | JSON Parse error: Unexpected EOF | End token encountered where a value is requiredOfficial WebKit parser message; LiteralParser.h supplies the JSON Parse error prefix |
| C# / System.Text.Json | The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. | Empty input, source resource stringOfficial .NET 10 resource string, path and location suffix omitted |
Causes you can reproduce
A response has no body
A 204 No Content response, a HEAD response or an empty file cannot be parsed as JSON. Handle a documented no-body result before calling the parser.
JSON.parse('');
// Also fails: a whitespace-only body
JSON.parse(' ');A write or transfer ends before the value is complete
An interrupted file write or response can leave a key without its value or a container without its closing delimiter. Compare the actual bytes with what the producer intended to send.
import json
json.loads('{"id":')Code builds only part of the JSON text
String concatenation can omit a closing bracket, quote or brace. Serialize the completed data structure instead of assembling its JSON text by hand.
const text = '{"items":[' + '1,2';
JSON.parse(text);How to fix it
- Inspect the raw text and its length. For HTTP input, inspect status and headers before parsing.
- Handle a documented 204 or HEAD response without parsing. Reject an empty body when the API contract requires JSON.
- For incomplete data, fix the producer, file write or transfer. Wait for the complete body; a network chunk is not necessarily one JSON document.
- Use a serializer to produce the complete value, then validate the saved or received text.
Serialize a complete value in JavaScript
Only treat missing content as an empty collection if the API contract explicitly defines that behavior. Otherwise report the missing body.
const text = JSON.stringify({ items: [1, 2] });
const data = JSON.parse(text);Check a local Python input before loading it
An empty input is different from the valid JSON literal null. Report empty input instead of silently replacing it.
import json
text = '' # replace with the file or response body
if not text.strip():
raise ValueError('Expected JSON, received an empty body')
data = json.loads(text)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
Is an empty string valid JSON?
A body containing zero characters is invalid JSON. A body containing two double-quote characters is a valid JSON string with an empty value.
Should I parse a 204 response?
No. HTTP 204 means No Content. Handle it as a no-body result according to your API contract.
Does Python Expecting value at char 0 always mean empty input?
No. Python also reports it for an HTML response or another invalid first character. Inspect the raw text to tell the causes apart.
Can I fix truncation by adding a closing brace?
Only if you know the complete intended data. A missing brace may be one symptom of lost values. Recover the complete file or response before changing it.
Related errors and tools
- Unexpected token '<' / DOCTYPE is not valid JSON
- "undefined" or "[object Object]" is not valid JSON
- JSONDecodeError: Extra data and multiple JSON values
- All JSON error guides
- JSON syntax reference
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.