json-type-extractor
v0.1.0
Published
Stream a JSON file and emit a TypeScript declaration (.d.ts) describing its structure. Designed for files larger than 50 MB.
Maintainers
Readme
json-type-extractor
Turn real JSON into useful TypeScript declarations — in one command.
Stream a file from disk, infer the structure it actually contains, and produce
a deterministic .d.ts without loading the complete JSON document into memory.
npx json-type-extractor@latest ./data.jsonJSON file → streaming inference → review-friendly TypeScript
Features
Built for the moment when a JSON file is real, useful, and too large or unfamiliar to model by hand.
| What you get | Why it matters |
| --- | --- |
| Zero-install CLI | Go from a JSON file to declarations immediately with npx. |
| Streaming input | Parse from disk without loading the complete source document. |
| Useful literal unions | Keep observed sets such as "draft" \| "published" instead of widening everything to string. |
| Named variants | Turn heterogeneous objects with fields such as type or kind into readable discriminated unions. |
| Deterministic output | Sorted properties and literal values keep generated diffs stable and reviewable. |
| Deep-schema-safe emission | Emit deeply nested inferred schemas through an iterative traversal. |
| ESM library API | Reuse the same inference and emission pipeline in TypeScript or JavaScript. |
Quick Start
npx json-type-extractor@latest ./data.jsonThat is the complete setup. The command writes data.json.d.ts next to the
input file.
From this JSON
{
"id": 1,
"environment": "prod",
"debugMode": true,
"features": ["search", "export"]
}To these declarations
The generated file starts with a generated-file comment, followed by:
declare module "data.json" {
export interface Data {
debugMode: boolean;
environment: "prod";
features: Array<"export" | "search">;
id: number;
}
const value: Data;
export default value;
}[!NOTE] The declaration describes the values observed in the input. It is not a validation schema and does not guarantee the shape of future data.
CLI Reference
npx json-type-extractor@latest <input.json> [options]| Option | Description | Default |
| --- | --- | --- |
| -o, --out <path> | Write the declaration to a custom path. | <input>.d.ts |
| -r, --root <name> | Name the exported root type. | Input filename stem in PascalCase |
| -m, --module <name> | Set the declare module "<name>" specifier. | Bare input filename |
| --no-module | Emit a plain declaration without a module wrapper. | Module wrapper enabled |
| --no-string-literals | Disable literal narrowing and discriminant-based variant naming. | Literal narrowing enabled |
| --string-literals-max <N> | Set the maximum distinct string literals retained per field before falling back to string. | 8 |
| -h, --help | Show CLI help. | — |
If --out points into another directory, that destination directory must
already exist.
Common recipes
Choose the output location and root type:
npx json-type-extractor@latest ./users.json --out ./types/users.d.ts --root UsersEmit a plain declaration without a module wrapper:
npx json-type-extractor@latest ./events.json --no-moduleMatch an application import specifier:
npx json-type-extractor@latest ./config.json --module "@app/config"Control string literal inference:
npx json-type-extractor@latest ./events.json --no-string-literals
npx json-type-extractor@latest ./events.json --string-literals-max 16Output Behavior
Module declarations
By default, the declaration is wrapped in declare module "<filename>", where the module name is the input's bare filename. For example, ./data/data.json uses "data.json". Use --module <name> to choose an explicit specifier or --no-module to emit a plain .d.ts file.
A bare module name does not automatically match every relative JSON import. Choose --module or --no-module to fit the imports and TypeScript configuration in your project.
String literal narrowing
String values are narrowed to a literal or literal union while no more than 8 distinct values have been observed at that schema node. Once the cap is exceeded, that node falls back to string. Use --string-literals-max <N> to change the cap, or --no-string-literals to emit all observed strings as string and disable discriminant-based variant naming.
Discriminated object variants
When heterogeneous objects have different key sets, the emitter looks for a shared single-literal string field with a distinct value in each variant. Candidate fields are tried in this exact order: type, kind, _type, __typename, then qualifying common fields in alphabetical order. Their literal values become interface names, producing unions such as:
export interface Leaf {
type: "leaf";
value: number;
}
export interface Branch {
children: unknown[];
type: "branch";
value: number;
}
export interface Tree {
nodes: Array<Leaf | Branch>;
}Objects with identical key sets merge into one shape instead of separate variants. Their observed field values are merged too, so a shared discriminant can become a literal union.
Programmatic API
Need the pipeline inside your own tooling? Install the package:
npm install json-type-extractorThe library API is ESM-only. Use package-root imports:
import { extractTypes } from "json-type-extractor";
const result = await extractTypes({
input: "users.json",
output: "users.json.d.ts",
rootName: "Users",
moduleName: "users.json",
useStringLiterals: true,
stringLiteralCap: 8,
});result contains the written outputPath, inferred schema, and generated declaration.
Want control over the two stages? Run inference and emission separately:
import { emitDeclaration, inferSchema } from "json-type-extractor";
const schema = await inferSchema("users.json", {
useStringLiterals: true,
stringLiteralCap: 8,
});
const declaration = emitDeclaration(schema, {
rootName: "Users",
moduleName: "users.json",
useStringLiterals: true,
});The package also exports Schema, defaultOutputPath, defaultRootName, and defaultModuleName from its public entry point.
How It Works
json-type-extractor feeds the file through a
stream-json token stream. As
tokens arrive, it accumulates observed primitive types, object key-set shapes,
array element schemas, and bounded string-literal sets. Object shapes with the
same structure are deduplicated; an iterative, deterministic emitter then turns
the result into a declaration.
Parsing is streamed, but memory use still depends on the complexity of the inferred schema and the number of unique shapes and observed values retained within their configured bounds.
Requirements
- Node.js 18 or newer.
- A regular JSON file path as input.
- ESM when using the library API.
Limitations
- Output is inferred from observed data; it does not validate future values and is not JSON Schema.
- Input from stdin, URLs, and gzip files is not supported.
- Parent directories for an output path are not created automatically.
- The default bare module specifier may not match project imports; use
--moduleor--no-modulewhen needed.
Maintainer documentation
See the npm publishing guide for the release workflow, first-package bootstrap, and Trusted Publishing setup.
License
Released under the repository's MIT License.
