Dependency-free JSON serialization for the raw-text content of an HTML
<script type="application/json"> element.
HTML parses script-element contents as raw text. A JSON string containing
</script> can therefore close the element before an application calls
JSON.parse. This package preserves the JSON value while escaping five
characters through one documented serialization pass.
Security boundary: this is not a general-purpose HTML sanitizer. It does not make data safe for attributes, event handlers, JavaScript source, CSS, URLs, or arbitrary HTML.
| Item | Current support |
|---|---|
| Release | v0.1.1 |
| Runtime | Node.js 22 or newer |
| Module format | ESM; CommonJS can use dynamic import() |
| Types | Bundled TypeScript declarations |
| Runtime dependencies | None |
| Licence | MIT |
The package is currently distributed from its versioned GitHub release tag:
npm install github:edilec/inline-json-for-html#v0.1.1Registry publication is intentionally not claimed until an npm release process and package ownership have been verified.
import { serializeInlineJson } from 'inline-json-for-html'
const value = {
message: '</script><script>alert("not executed")</script>',
}
const serialized = serializeInlineJson(value)
const html = `<script type="application/json" id="page-data">${serialized}</script>`Read the value as data, not executable JavaScript:
const element = document.querySelector('#page-data')
const valueAgain = JSON.parse(element.textContent)For a CommonJS application, load the ESM package with dynamic import:
async function render() {
const { serializeInlineJson } = await import('inline-json-for-html')
return serializeInlineJson({ status: 'ready' })
}See examples/render-page.mjs for a complete,
locally runnable example.
Returns JSON text suitable for the text content of an HTML script element.
serializeInlineJson(value, {
replacer: ['message'],
space: 2,
})Options are a plain object with only replacer, space, and
onUnsupported as own enumerable data properties; unknown or inherited
options are rejected rather than silently changing the omission policy.
The supported replacer and spacing behavior follows native JSON.stringify,
except space must be an integer from 0 through 10. A top-level value
that cannot be represented as JSON throws instead of returning undefined, and
the error distinguishes an undefined value from an unsupported type.
| Input | Result |
|---|---|
Object, array, string, finite number, boolean, null |
Serialized |
Date, or any value with toJSON |
Serialized via toJSON |
Non-finite number (NaN, ±Infinity) |
null |
undefined, function, symbol — top level |
Throws, naming the category |
undefined, function, symbol — nested |
See onUnsupported below |
BigInt |
Throws a value-free TypeError |
| Cyclic structure | Throws a value-free TypeError |
JSON cannot represent undefined, functions or symbols. Native
JSON.stringify drops such a property from an object and turns such an element
into null in an array — silently, so embedded page data can end up missing
fields the author expected to be there.
'omit'(default) keeps that native omission behavior for successful serialization.'throw'reports the value and its source position instead:
serializeInlineJson({ user: { id: 1, email: undefined } }, {
onUnsupported: 'throw',
})
// TypeError: Cannot represent the undefined value at property[0].property[1] as JSON;
// remove it or serialize with onUnsupported: 'omit'.Object positions are zero-based in JSON.stringify visitation order; array
positions keep their numeric indices (for example, property[0][2]). Raw
property names are never copied into diagnostics, because they may contain
control characters or private data. Distinct object and array locations remain
distinguishable.
Values are inspected after toJSON and after a function replacer, so a value
that becomes unrepresentable is reported too.
onUnsupported: 'throw' requires a function replacer or no replacer. An array
replacer is a property allowlist that omits properties by design, so combining
the two is rejected rather than given a confusing meaning.
| Character | Output | Reason |
|---|---|---|
< |
\u003C |
Prevents an HTML end-tag opener |
> |
\u003E |
Keeps HTML-significant text escaped consistently |
& |
\u0026 |
Avoids HTML-significant ampersands in embedded data |
| U+2028 | \u2028 |
Produces stable escaped JSON across consumers |
| U+2029 | \u2029 |
Produces stable escaped JSON across consumers |
JSON.parse reconstructs the original values. Keys, values, replacer output,
and toJSON output pass through the same final escaping step.
| Destination | Supported? | Required approach |
|---|---|---|
<script type="application/json"> text |
Yes | Serialize here; read with textContent and JSON.parse |
| JavaScript source expression | No | Use a JavaScript-aware serializer and CSP review |
| HTML attribute | No | Quote and encode for the exact attribute context |
| Event-handler attribute | No | Do not embed untrusted data there |
| HTML text or markup | No | Use text nodes or a reviewed HTML sanitizer |
| URL | No | Validate and encode for the URL component |
| CSS | No | Avoid embedding untrusted data; use a CSS-aware control |
The detailed security assumptions and abuse cases are in
docs/threat-model.md.
- Cyclic values and
BigIntvalues throw. - Unsupported object properties are omitted by default; set
onUnsupported: 'throw'to be told about them instead. - Non-finite numbers become
null. - Getters,
toJSON, and replacer callbacks execute normally. - A replacer or
toJSONimplementation can have side effects; this package does not isolate user code. - Errors thrown during serialization are replaced with a value-free
TypeErrorunless they are this package's own positional strict-mode diagnostic. Native cycle errors can otherwise echo private property names, and callback errors can carry arbitrary input. The original error and cause are not attached.
application value
│
▼
JSON.stringify(value, replacer, space)
│
▼
escape < > & U+2028 U+2029 in the serialized text
│
▼
HTML script-element text ──textContent──▶ JSON.parse
The implementation deliberately has no DOM parser, HTML sanitizer, template
engine, or runtime dependency. See docs/architecture.md.
Node.js 22, 24, and 26 are tested in CI. Browser use concerns the generated
JSON text, not a browser runtime build of this package. Bundler, Deno, Bun, and
older-Node support are not currently claimed. See
docs/compatibility.md.
npm run check
npm run examplenpm run check verifies syntax, local documentation links, tests, 100% line,
branch and function coverage for the implementation, and the package allowlist.
- Review the threat model before using a new context.
- Report vulnerabilities through the private route in SECURITY.md.
- Read SUPPORT.md for usage questions and project scope.
- Read CONTRIBUTING.md before proposing a change.
- Maintainer responsibilities are recorded in MAINTAINERS.md.
- The reviewed release procedure is documented in docs/releasing.md.
MIT © 2026 Edilec Private Limited
Maintained by Edilec.