{"id":47667955,"url":"https://github.com/kagal-dev/json-template","last_synced_at":"2026-04-02T12:04:11.670Z","repository":{"id":346202629,"uuid":"1188337549","full_name":"kagal-dev/json-template","owner":"kagal-dev","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-22T20:13:38.000Z","size":75,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-23T10:13:05.316Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kagal-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-21T23:51:01.000Z","updated_at":"2026-03-22T22:24:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kagal-dev/json-template","commit_stats":null,"previous_names":["kagal-dev/json-template"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/kagal-dev/json-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kagal-dev%2Fjson-template","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kagal-dev%2Fjson-template/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kagal-dev%2Fjson-template/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kagal-dev%2Fjson-template/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kagal-dev","download_url":"https://codeload.github.com/kagal-dev/json-template/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kagal-dev%2Fjson-template/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31305971,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-02T12:04:09.255Z","updated_at":"2026-04-02T12:04:11.657Z","avatar_url":"https://github.com/kagal-dev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @kagal/json-template\n\nA TypeScript template engine for JSON documents with\nshell-style `${var:-default}` variable substitution.\nCompiles once, renders to native JavaScript objects —\ntypes are preserved, not stringified.\n\n## Why\n\nJavaScript template literals handle string interpolation\nwell but don't understand JSON structure — a number like\n`${port}` can silently become the string `\"8080\"`,\nspecial characters in strings break JSON syntax, and\nthere's no way to list which variables a template\nexpects.\n\nThis engine treats JSON structure as a first-class\nconcern. It parses the template at compile time,\nunderstands whether each variable sits in a bare value\nposition or inside a string, and assembles a native JS\nobject at render time — no string concatenation of JSON,\nno `JSON.parse` at render time.\n\n## Installation\n\n```bash\nnpm install @kagal/json-template\n```\n\n## Usage\n\n```ts\nimport { compile } from '@kagal/json-template';\n\nconst tpl = compile(\n  '{\"host\": \"${host:-localhost}\", \"port\": ${port:-3000}}'\n);\n\ntpl.render({});\n// → { host: \"localhost\", port: 3000 }\n\ntpl.render({ host: \"10.0.0.1\", port: 8080 });\n// → { host: \"10.0.0.1\", port: 8080 }\n\ntpl.toJSON({}, 2);\n// → pretty-printed JSON string\n```\n\n### Bare vs embedded variables\n\n**Bare** variables (outside JSON strings) preserve\ntheir native type:\n\n```ts\ncompile('{\"port\": ${port}}').render({ port: 8080 })\n// → { port: 8080 }   ← number, not string\n```\n\n**Embedded** variables (inside `\"...\"`) concatenate as\nstrings:\n\n```ts\ncompile('{\"addr\": \"${host}:${port}\"}')\n  .render({ host: \"localhost\", port: 3000 })\n// → { addr: \"localhost:3000\" }\n```\n\nThe position is determined at compile time by tracking\nJSON string context, not with a runtime heuristic.\n\n### Shell-style defaults\n\nVariables can specify a fallback value using the `:-`\nseparator, matching POSIX shell parameter expansion:\n\n```ts\ncompile(\n  '{\"host\": \"${host:-localhost}\", \"port\": ${port:-3000}}'\n).render({})\n// → { host: \"localhost\", port: 3000 }\n```\n\nFor bare variables, defaults are JSON-parsed to preserve\ntype: `${port:-3000}` defaults to the number `3000`,\n`${flag:-true}` to the boolean `true`. If the default\nisn't valid JSON, it falls back to a plain string.\n\nFor embedded variables, defaults are always treated as\nstrings (they're inside a `\"...\"` already).\n\n### Defaults with nested JSON\n\nDefault values can contain nested JSON with balanced\nbraces:\n\n```ts\ncompile('{\"cfg\": ${cfg:-{\"retries\":3}}}').render({})\n// → { cfg: { retries: 3 } }\n```\n\n### Dotted key paths\n\nVariable names can use dotted notation to traverse\nnested context objects. Resolution only follows own\nproperties, so inherited keys like `toString`,\n`constructor`, and `__proto__` are treated as missing:\n\n```ts\ncompile('{\"h\": \"${server.host}\"}')\n  .render({ server: { host: \"10.0.0.1\" } })\n// → { h: \"10.0.0.1\" }\n```\n\n### Static analysis\n\nExtract variable metadata without compiling (does not\nrequire valid JSON):\n\n```ts\nimport { listVariables } from '@kagal/json-template';\n\nlistVariables('{\"a\": \"${name}\", \"b\": ${port:-3000}}')\n// → [\n//   { name: \"name\", bare: false, ... },\n//   { name: \"port\", bare: true, defaultValue: \"3000\", ... }\n// ]\n```\n\n### Strict mode\n\n```ts\ncompile('{\"v\": ${required}}', { strict: true }).render({})\n// throws UnresolvedVariableError\n```\n\n## API\n\n### `compile(template, options?)`\n\nParses and compiles a JSON template string. Returns a\n`Template` instance.\n\n```ts\nconst tpl = compile(\n  '{\"port\": ${port:-3000}, \"host\": \"${host:-localhost}\"}'\n);\n\ntpl.variables  // readonly TemplateVariable[]\ntpl.names      // ReadonlySet\u003cstring\u003e\n\ntpl.render({})              // → { port: 3000, ... }\ntpl.render({ port: 8080 })  // → { port: 8080, ... }\ntpl.toJSON({})              // → JSON string\ntpl.toJSON({}, 2)           // → pretty-printed\n```\n\n**Options:**\n\n| Option   | Default | Description                     |\n|----------|---------|---------------------------------|\n| `strict` | `false` | Throw `UnresolvedVariableError` when a variable has no value and no default. When `false`, bare unresolved variables become `null` and embedded ones become `\"\"`. |\n\n### `listVariables(template)`\n\nStatic analysis only — extracts variable metadata\nwithout requiring valid JSON. Useful for tooling,\ndocumentation generation, or validation.\n\n```ts\nlistVariables('{\"a\": \"${name}\", \"b\": ${port:-3000}}')\n// → [\n//   { name: \"name\", bare: false, ... },\n//   { name: \"port\", bare: true, ... }\n// ]\n```\n\n### `TemplateVariable`\n\nEach variable occurrence exposes:\n\n| Field          | Type      | Description            |\n|----------------|-----------|------------------------|\n| `raw`          | `string`  | Full expression text   |\n| `name`         | `string`  | Variable name          |\n| `defaultValue` | `string?` | Raw default after `:-` |\n| `bare`         | `boolean` | Bare vs embedded       |\n| `offset`       | `number`  | `$` offset in source   |\n\n### Variable name rules\n\nNames are dot-separated segments where each segment\nmatches `/^[a-zA-Z_][a-zA-Z0-9_-]*$/` — letters,\ndigits, underscores, and hyphens, starting with a\nletter or underscore. Dots delimit path segments for\nnested context traversal.\n\n### Errors\n\n| Error                    | When                       |\n|--------------------------|----------------------------|\n| `TemplateParseError`     | Unterminated `${`, empty expression, invalid name, variable in key, reserved sentinel character, or invalid JSON after extraction |\n| `UnresolvedVariableError`| `strict: true` and no value or default |\n\n### `jsonNull`\n\nThe `null` value used by bare unresolved variables\n(when `strict` is `false`). Use it when you need to\nexplicitly pass or check for JSON null:\n\n```ts\nimport { compile, jsonNull } from '@kagal/json-template';\n\ncompile('{\"v\": ${missing}}').render({})\n// → { v: null }  (jsonNull)\n```\n\n### `isNull(value)`\n\nType guard — returns `true` when `value` is `null`.\n\n### Unresolved variable behaviour\n\n| Position           | `strict: true`  | `strict: false` |\n|--------------------|-----------------|-----------------|\n| Bare (no default)  | throws          | `null`          |\n| Embedded (no def.) | throws          | `\"\"`            |\n| Any (with default) | uses default    | uses default    |\n\n### Embedded non-primitive coercion\n\nWhen an object or array is resolved in an embedded\n(string) position, it is serialised via\n`JSON.stringify` rather than `String()`:\n\n```ts\ncompile('{\"msg\": \"config=${cfg}\"}')\n  .render({ cfg: { retries: 3 } })\n// → { msg: 'config={\"retries\":3}' }\n//   not 'config=[object Object]'\n```\n\nPrimitives (`number`, `boolean`, `null`) use\n`String()` as expected.\n\n## Provenance\n\nPublished with\n[npm provenance](https://docs.npmjs.com/generating-provenance-statements)\nvia GitHub Actions OIDC — no long-lived tokens involved.\n\n## Licence\n\n[MIT](LICENCE.txt)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkagal-dev%2Fjson-template","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkagal-dev%2Fjson-template","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkagal-dev%2Fjson-template/lists"}