DIYgod/RSSHub · error · Error

Failed to parse embedded script JSON: ${error.message}

Error message

Failed to parse embedded script JSON: ${error.message}

What it means

Thrown when the double JSON.parse of the data-iso script text fails. ScienceRedirect stores its payload as a JSON string inside a JSON envelope (hence JSON.parse(JSON.parse(...))), and any malformed/oddly-encoded content surfaces here with the underlying parse error attached via the cause option.

Source

Thrown at lib/routes/sciencedirect/call-for-paper.tsx:41

    description: '`sciencedirect.com/browse/calls-for-papers?subject=education` -> `/sciencedirect/call-for-paper/education`',
};

async function handler(ctx) {
    const { subject = '' } = ctx.req.param();
    const apiUrl = `https://www.sciencedirect.com/browse/calls-for-papers?subject=${subject}`;
    const response = await got(apiUrl);
    const $ = load(response.body);

    const scriptJSON = $('script[data-iso-key="_0"]').text();
    if (!scriptJSON) {
        throw new Error('Cannot find the script with data-iso-key="_0"');
    }

    let data;
    try {
        data = JSON.parse(JSON.parse(scriptJSON));
    } catch (error: any) {
        throw new Error(`Failed to parse embedded script JSON: ${error.message}`, { cause: error });
    }

    const cfpList = data?.callsForPapers?.list || [];
    if (!cfpList.length) {
        throw new Error('No Calls for Papers found');
    }

    const items = cfpList.map((cfp) => {
        const link = `https://www.sciencedirect.com/special-issue/${cfp.contentId}/${cfp.url}`;
        const description = renderToString(
            <div>
                <p>
                    <strong>Summary:</strong> {cfp.summary}
                </p>
                <p>
                    <strong>Submission Deadline:</strong> {cfp.submissionDeadline}
                </p>
                <p>

View on GitHub (pinned to bed535e087)

Solutions

  1. Log scriptJSON to inspect its actual encoding (single vs double JSON, HTML-escaping).
  2. If single-encoded now, replace JSON.parse(JSON.parse(scriptJSON)) with a single JSON.parse.
  3. If the content is HTML-escaped, unescape (he.decode) before parsing.
  4. Keep the try/catch but include the body length and a snippet in the rethrown error.

Example fix

// before
try {
    data = JSON.parse(JSON.parse(scriptJSON));
} catch (error: any) {
    throw new Error(`Failed to parse embedded script JSON: ${error.message}`, { cause: error });
}

// after — try double, fall back to single encoding
try {
    try {
        data = JSON.parse(JSON.parse(scriptJSON));
    } catch {
        data = JSON.parse(scriptJSON);
    }
} catch (error: any) {
    throw new Error(`Failed to parse embedded script JSON: ${error.message}`, { cause: error });
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Normalize the script text (unescape HTML entities) and try single then double parse.
function parseIsoPayload(raw: string): unknown {
    const unescaped = raw.replace(/&quot;/g, '"').replace(/&amp;/g, '&');
    try {
        return JSON.parse(unescaped);
    } catch {
        return JSON.parse(JSON.parse(unescaped));
    }
}

Type guard

const isPlainObject = (v: unknown): v is Record<string, unknown> =>
    typeof v === 'object' && v !== null && !Array.isArray(v);

Try / catch

let data: unknown;
try {
    data = JSON.parse(JSON.parse(scriptJSON));
} catch {
    try {
        data = JSON.parse(scriptJSON); // single-encoded fallback
    } catch (error: any) {
        throw new Error(`Failed to parse embedded script JSON: ${error.message}`, { cause: error });
    }
}

Prevention

When it happens

Trigger: scriptJSON is non-empty but JSON.parse(JSON.parse(scriptJSON)) throws — the inner parse produces a non-string, or the outer parse hits invalid JSON (e.g. the payload is now single-encoded HTML-escaped, or contains unescaped characters).

Common situations: ScienceDirect switched from double-encoded to single-encoded JSON; the payload now contains escaped sequences that break strict parsing; a partial/chunked response produced truncated JSON; cheerio's .text() decoded entities the encoder didn't expect.

Understand the failure class

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/a4e9691bf77e8311. Report an issue: GitHub.