Apply DeepReadonly to JSON-Generated TypeScript Types
Use json to typescript with a DeepReadonly utility to recursively freeze nested objects and arrays — perfect for Redux state and config files.
Detailed Explanation
Shallow vs Deep Readonly
Readonly<T> only freezes the top level. Nested objects and arrays remain mutable, which is rarely what you want for state slices loaded from JSON config or API responses.
Example JSON
{
"site": {
"name": "DevToolbox",
"locales": ["en", "ja"],
"theme": { "primary": "#3b82f6", "radius": 8 }
},
"features": [
{ "key": "betaSearch", "enabled": true }
]
}
Generated TypeScript
interface Theme {
primary: string;
radius: number;
}
interface Site {
name: string;
locales: string[];
theme: Theme;
}
interface Feature {
key: string;
enabled: boolean;
}
interface Config {
site: Site;
features: Feature[];
}
A DeepReadonly Utility
type DeepReadonly<T> = T extends (infer U)[]
? ReadonlyArray<DeepReadonly<U>>
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
type FrozenConfig = DeepReadonly<Config>;
FrozenConfig rejects config.features.push(...), config.site.theme.primary = "#000", and config.site.locales[0] = "fr" — all at compile time.
When DeepReadonly Pays Off
- Redux/Zustand state — Catches accidental in-place mutations that bypass the reducer.
- Config files loaded once at boot — Prevents code from accidentally rewriting site settings.
- Snapshot testing — Guarantees a snapshot value cannot be mutated between assertions.
Caveats
- DeepReadonly is purely a compile-time view.
Object.freezeis needed for runtime enforcement. - It does not handle
Map,Set, or class instances out of the box — extend the conditional types if your config uses them. - Applying DeepReadonly to a discriminated union still works because the conditional type recurses through
objectfirst, then literal types pass through unchanged.
Pair this utility with the JSON-generated types and you get one immutable view of your entire config tree from a single Config interface.
Use Case
Loading a site configuration JSON at server start in Next.js and ensuring no request handler can accidentally mutate the config tree halfway through rendering.
Try It — JSON to TypeScript
Related Topics
Generate Readonly TypeScript Interfaces from JSON
Advanced Patterns
Apply Partial, Required, and Readonly to JSON-Generated Types
TypeScript Patterns
Generate Branded Types from JSON IDs in TypeScript
TypeScript Patterns
Use Pick and Omit to Narrow JSON-Generated Types
TypeScript Patterns
Type HTTP PATCH Payload Shapes from JSON
TypeScript Patterns