Why it happens
JSON.parse() converts its argument to a string, and String(undefined) is the word undefined, which JSON does not have. null is different: JSON.parse(null) returns null, so reading a localStorage key that was never set is safe. Common sources, in rough order:
1. The text “undefined” was saved earlier. Storage APIs convert values to strings, and JSON.stringify(undefined) returns undefined, not a string, so the word itself gets stored. A React effect that saves state before it loads does exactly this:
import { useEffect, useState } from 'react';
function App() {
const [session, setSession] = useState(); // undefined until loaded
useEffect(() => {
localStorage.setItem('session', JSON.stringify(session)); // first run stores "undefined"
}, [session]);
// ...
}
On the next page load, JSON.parse(localStorage.getItem('session')) throws.
2. A field or variable is missing. The API renamed payload to data, req.body is undefined because no body parser runs, or a Lambda test event has no body, so JSON.parse(event.body) gets undefined.
3. An environment variable is not set. JSON.parse(process.env.FEATURE_FLAGS) works locally and fails in CI or Docker. In Vite, client code only sees variables whose names start with VITE_; any other name reads as undefined.
4. A tool variable does not exist. Postman’s pm.environment.get('session') returns undefined for a missing variable. In RPG Maker MV and MZ, PluginManager.parameters('Name') returns an empty object when Name does not match the plugin’s file name, so every parameter is undefined.
If undefined sits inside a larger text, as in {"total": undefined}, Chrome reports Unexpected token 'u', "{"total": undefined}" is not valid JSON instead. That JSON was built by joining strings; use JSON.stringify.
How to fix “undefined” is not valid JSON
Log the value before the parse, console.log(typeof raw, raw), then fix its source.
localStorage and React
Never store undefined, and treat an old bad value as empty:
function saveSession(session) {
if (session === undefined) localStorage.removeItem('session');
else localStorage.setItem('session', JSON.stringify(session));
}
function loadSession() {
const saved = localStorage.getItem('session');
if (saved === null || saved === 'undefined') return null;
return JSON.parse(saved);
}
In the component: useState(loadSession) and useEffect(() => saveSession(session), [session]).
Environment variables
Default optional settings, and fail clearly on required ones:
const flags = JSON.parse(process.env.FEATURE_FLAGS ?? '{}');
const raw = process.env.DB_CONFIG;
if (raw === undefined) throw new Error('DB_CONFIG is not set');
const dbConfig = JSON.parse(raw);
A variable that is set but empty gives Unexpected end of JSON input instead.
Request bodies
In Express, req.body stays undefined until a body parser runs. Add app.use(express.json()) and use req.body as an object, without JSON.parse; parsing it again gives “[object Object]” is not valid JSON.
Postman
const raw = pm.environment.get('session');
const session = raw ? JSON.parse(raw) : {};
pm.environment.set('session', JSON.stringify(session));
Environment values are strings, so store objects with JSON.stringify, as on the last line.
RPG Maker plugins
Keep the plugin’s file name and the name passed to PluginManager.parameters() identical.
Same error in other languages
- Python:
json.loads(None)raisesTypeError: the JSON object must be str, bytes or bytearray, not NoneType. - Safari words this case
Unexpected identifier "undefined". Its other common JSON message, Unrecognized token ‘<’, means HTML arrived instead.
To check a stored or logged value, paste it into the JSON validator. Other parse errors: common JSON errors.