JSON.parse Returns a String Instead of an Object — Why and Fix

You call JSON.parse(data) and instead of getting a JavaScript object, you get back a string. No error is thrown, but the result is wrong. The cause is almost always double-encoded JSONJSON.stringify() was called twice on the same data.

What double-encoding looks like

const original = { name: "Alice", age: 30 };

// Normal encoding — produces a JSON string
const encoded = JSON.stringify(original);
// encoded = '{"name":"Alice","age":30}'

// Double encoding — stringify is called again on an already-stringified value
const doubleEncoded = JSON.stringify(encoded);
// doubleEncoded = '"{\\"name\\":\\"Alice\\",\\"age\\":30}"'
// Notice: outer quotes + all inner quotes are escaped with backslash

Now when you parse the double-encoded value:

const result = JSON.parse(doubleEncoded);

typeof result;   // "string" ← still a string!
console.log(result); // '{"name":"Alice","age":30}' ← the inner JSON string

// You need to parse once more to get the object
const obj = JSON.parse(result);
typeof obj;   // "object" ✅
obj.name;     // "Alice" ✅

How to detect it

const result = JSON.parse(data);

if (typeof result === 'string') {
    // Double-encoded — need to parse again
    console.warn('Data was double-encoded');
    const obj = JSON.parse(result);
} else {
    // result is the object you expected
}

Where double-encoding typically happens

1. Server-side: JSON.stringify on an already-serialized value

// Node.js / Express — common mistake
app.get('/api/user', (req, res) => {
    const user = { name: "Alice" };
    const json = JSON.stringify(user);  // json is now a string

    // ❌ res.json() calls JSON.stringify internally — double encoding
    res.json(json);

    // ✅ Either use res.json() with the object directly
    res.json(user);

    // ✅ Or use res.send() with the pre-stringified string
    res.set('Content-Type', 'application/json').send(json);
});

2. Storing and retrieving from localStorage

// ❌ Storing a pre-stringified value, then stringifying again
const data = JSON.stringify({ name: "Alice" });
localStorage.setItem('user', JSON.stringify(data)); // double-encoded

const raw = localStorage.getItem('user');
JSON.parse(raw); // → string, not object

// ✅ Correct — stringify the object, not the string
localStorage.setItem('user', JSON.stringify({ name: "Alice" }));
const user = JSON.parse(localStorage.getItem('user')); // → object ✅

3. API gateway or middleware double-serializing

// Some API gateways (AWS API Gateway, certain Express middleware)
// serialize the body automatically. If your handler also stringifies,
// the client receives a double-encoded string.

// ❌ AWS Lambda — body is already serialized by API Gateway
return {
    statusCode: 200,
    body: JSON.stringify(JSON.stringify({ name: "Alice" })) // ← double
};

// ✅ Correct
return {
    statusCode: 200,
    body: JSON.stringify({ name: "Alice" }) // ← single
};

4. Passing a JSON string through JSON.stringify in a query parameter

const filter = JSON.stringify({ status: "active" });

// ❌ Stringifying again when building the URL
const url = `/api/users?filter=${JSON.stringify(filter)}`;

// ✅ Correct — encode the already-stringified string for the URL
const url = `/api/users?filter=${encodeURIComponent(filter)}`;

The defensive parse pattern

If you cannot immediately fix the double-encoding at the source, use a safe parser that handles both cases:

function safeParse(value) {
    if (typeof value !== 'string') return value; // already an object
    try {
        const parsed = JSON.parse(value);
        // If result is still a string, it was double-encoded
        if (typeof parsed === 'string') {
            return JSON.parse(parsed);
        }
        return parsed;
    } catch {
        throw new SyntaxError('Invalid JSON: ' + value.slice(0, 100));
    }
}

The real fix is always to find where the extra JSON.stringify() call is happening and remove it. The defensive parser is a temporary measure — double-encoding in a data pipeline is a bug, not a feature.

Inspect the raw value

Paste the raw value your API returns into the JSON Formatter. If the formatted output is a single quoted string (like "{\\"name\\":\\"Alice\\"}"), the data is double-encoded. If it shows a proper object tree, the data is correctly encoded and the problem is somewhere in your parsing code.

Frequently Asked Questions

Why does JSON.parse return a string instead of an object?

The JSON was double-encoded — JSON.stringify was called twice on the same data. The outer JSON.parse decodes the outer layer, leaving you with a JSON string instead of an object. You need to call JSON.parse a second time to get the actual object.

How do I detect double-encoded JSON?

After calling JSON.parse, check the type of the result: typeof result === 'string'. You can also look at the raw value — double-encoded JSON starts and ends with escaped quotes: "{\"key\":\"value\"}".

How do I fix double-encoded JSON quickly?

Parse twice: const obj = JSON.parse(JSON.parse(doubleEncodedString)). The permanent fix is to remove the extra JSON.stringify() call wherever it is being added in your server or middleware code.

Can res.json() in Express cause double encoding?

Yes. res.json() calls JSON.stringify() internally. If you pass it a string that is already a JSON string (because you called JSON.stringify() manually), the result is double-encoded. Pass the raw object to res.json(), or use res.send() with the pre-stringified string and set Content-Type: application/json yourself.