octobercms/october · error · Error

Broken JSON number body near ${body}

Error message

Broken JSON number body near ${body}

What it means

The number branch of getBody() in the lenient JSON scanner: it collects characters from the set [0-9 + - .] and only returns when it reaches a character outside that set. If the input ends while the scan is still inside the number — no closing brace, quote, comma or whitespace terminates it — the loop falls off the end of the string and throws with the digits collected so far. Because the surrounding object parser also relies on a terminator, a shorthand attribute whose LAST value is a bare number and whose closing brace is missing will always hit this.

Source

Thrown at modules/system/assets/js/framework.js:2258

            originLength: "null".length,
            body: "null"
          };
        }
        throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10));
      }
      if (str[pos] === "-" || str[pos] === "+" || str[pos] === "." || str[pos] >= "0" && str[pos] <= "9") {
        var body = "";
        for (var i = pos; i < str.length; i++) {
          if (str[i] === "-" || str[i] === "+" || str[i] === "." || str[i] >= "0" && str[i] <= "9") {
            body += str[i];
          } else {
            return {
              originLength: body.length,
              body
            };
          }
        }
        throw new Error("Broken JSON number body near " + body);
      }
      if (str[pos] === "{" || str[pos] === "[") {
        var stack = [str[pos]];
        var body = str[pos];
        for (var i = pos + 1; i < str.length; i++) {
          body += str[i];
          if (str[i] === "\\") {
            if (i + 1 < str.length) body += str[i + 1];
            i++;
          } else if (str[i] === '"') {
            if (stack[stack.length - 1] === '"') {
              stack.pop();
            } else if (stack[stack.length - 1] !== "'") {
              stack.push(str[i]);
            }
          } else if (str[i] === "'") {
            if (stack[stack.length - 1] === "'") {
              stack.pop();

View on GitHub (pinned to b608633a7e)

Solutions

  1. Add the closing brace so the number is terminated: use the fully braced form data-request-data="{id: 5, page: 2}".
  2. Check the reported 'near <body>' digits against your attribute to confirm which value ran to the end.
  3. If the brace is present in source but missing at runtime, inspect the rendered HTML — a stray quote or templating bug is truncating the attribute.

Example fix

// before — last value runs to end of input
<a data-request="onPaginate" data-request-data="id: 5, page: 2">Next</a>

// after
<a data-request="onPaginate" data-request-data="{id: 5, page: 2}">Next</a>
Defensive patterns

Strategy: validation

Validate before calling

// Shorthand values whose last value is numeric MUST carry their own closing brace
function needsClosingBrace(value) {
    var v = value.trim();
    return v.charAt(0) !== '{' && /[0-9.\-+]\s*$/.test(v);
}

Prevention

When it happens

Trigger: data-request-data="id: 5, page: 2" — paramToObj wraps it as {id: 5, page: 2}, the final '2' runs to end-of-input and throws; data-request-data="{id: 25" (explicit opening brace, missing close); oc.parseJSON('[1, 2').

Common situations: Using the convenient shorthand form (key: value without braces) where the last value is numeric — the wrapper only adds '{' and '}', but the trailing '}' is what the number scanner needs as terminator, so any omission truncates the number; partials rendered dynamically where the closing '}' gets dropped by templating or escaping.

Related errors


AI-assisted analysis of octobercms/october@b608633a7e (2026-08-21). Data as JSON: /api/errors/4fc05722b02b960f. Report an issue: GitHub.