Repository navigation
Treat JSON types more literally #26552
Description
Activity
- addedWorking as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
on Aug 20, 2018 RyanCavanaugh commented
on Aug 20, 2018 MemberMore actionsSince the JSON is almost an object literal
It is except where it isn't, and when it isn't, it really isn't. You wouldn't want to
requirein a JSON file of customer data and see"Jason Bobsworth" | "Alice Freelan" | "Jane Costo" | "Roger Mister" | "Ted Laker" | "Frank Frankly" | "(600 more...)"in type information.There are some good tools like https://quicktype.io/ that can configurably generate types from JSON; I'd recommend using something like that to "write down" the shape of the JSON you want to see.
Reacted by Balázs Édes, Peter Grassberger, Will Bamford and Vincent RubinettiReacted by Jarrod Davis, Lucas Forster, jsus, Jakub Kozłowski, Mirul, Eiriksm0raas, Michael Absolon, Ezra Ashenafi and Yurii HaiovyiI'm talking specifically about raw JSON files using Typescript's native JSON typing.
I am in fact usingjson-schema-to-typescript(as seen in example) for times when I am working with dynamic JSON, I was specifically talking about the static case.
Can you clarify why I wouldn't want to see"Jason Bobsworth" | "Alice Freelan" | "Jane Costo" | "Roger Mister" | "Ted Laker" | "Frank Frankly" | "(600 more...)"in type information?Reacted by Jarrod Davis, jsus, Eiriksm0raas, Ezra Ashenafi and Yurii HaiovyiIt does seem like a much deeper problem indeed, since even within the same file, even this:
const string = 'text' const object = { string }
results in a
const object: { string: string; }type.
RyanCavanaugh commented
on Aug 20, 2018 MemberMore actionsConsider if you did something like this:
import customers = require("./customers.json"); // Error: "Bob Bobbily" is not assignable to "Jason Bobsworth" | "Alice Freelan" | "Jane Costo" |... customers.push({ name: "Bob Bobbily" });
You might want to read https://blog.mariusschulz.com/2017/02/04/typescript-2-1-literal-type-widening or #20195
Reacted by Ka MokExtremely informative link, thank you.
I see there are indeed some downsides.Is there any solution for this use case (other than casting)?
sample.json
{ "type": "frog" }sample.ts
import sample from './sample.json' type AnimalType = 'frog' | 'cat' | 'dog' type Animal = { type: AnimalType } const a: Animal = sample // compile error
What I'm trying to do is testing generated types (let's say
Animalin the example) against hand written JSON samples. My issue with just simply casting:const a = sample as Animal
Is that it can mask some other "type incorrectness" in the generated types.
Reacted by Mihail Malo, Benny Neugebauer, Mariano Pardo, melissafzhang, DarkLite1, Webber Wang, Jarrod Davis, nicholas a. evans, James Bromwell, Nat Zimmermann and 7 moreBalázs Édes (@bali182) That's literally the problem I had. It seems that this option is not currently supported.
If you just want to test your typings, you might get away with using the JSON as an object literal:type AnimalType = 'frog' | 'cat' | 'dog' type Animal = { type: AnimalType } const a: Animal = <%= sample_json_as_text %>
for
type AnimalType = 'frog' | 'cat' | 'dog' type Animal = { type: AnimalType } const a: Animal = { "type": "frog" }
which would pass.
Reacted by Balázs Édes, melissafzhang, Wenchen Li and Jay BeeBeing bitten by this over and over again, sometimes have to resort to generating something like this from the JSON:
interface _Common { "json": { "literal": ["here"] } } type RecursiveReadonly<T> = T extends object ? { readonly [K in keyof T]: RecursiveReadonly<T[K]> } : T type Common = RecursiveReadonly<_Common> export { Common }
This types all the arrays as tuples as well, which is pretty nice.
Would be much nicer though if I just had an option to import the JSON module like this.
This do the trick :
const data: Data = JSON.parse(JSON.stringify(Your_Data_Json));Reacted by S. B. Tam, Mihail Malo, Trotyl Yu, Balázs Édes, Elizabeth Bruce, Eudes Petonnet-Vincent, Michael Scott-Nelson, Alexey Korchevsky, Webber Wang, kb1000 and 24 moreReacted by Valery Zinchenko, Wenchen Li, Shiba, jase, Punit Shah, Jonathan Holford, Lou Huang, Michael Absolon and DavidReacted by WhereJulyRyan Cavanaugh (@RyanCavanaugh) Since we can now assert const - is there any possibility this will be re-considered in the future?
Something like:
import Schema from './schema.json' as const;
Would be amazing.
Reacted by Ben, Sébastien Lorber, Mihail Malo, Eudes Petonnet-Vincent, dave kraftsow, Irvin Zhan, Damian Bouné, Benny Neugebauer, H Tsang, Aleksi Pekkala and 90 moreReacted by Mikolaj Figurski, antonmorya, Eric McDaniel, Felix Gnass, Frank Weindel, Alexandros Pappas, Jacob Padgett, Joshua Passos and Pengő DzsóI'm wanting the same: ability to import a json as a const literal and get appropriate union types.
My json file is a configuration, which contains a list of available app locales. I want the union type instead of just "string"
Edit: created a feature request as it did not seem to exist yet: #32063
Reacted by H Tsang, Michael Scott-Nelson, Webber Wang, Stepan Mikhailiuk, Jarrod Davis, Oloompa, James Bromwell, Lucas Forster, Hector Ayala, Daniel Schultz and 7 moreI'm running into exactly the same issue: need to check my test data stored in JSON files against auto-generated types from schema. Would love to see a solution for this.
Reacted by Ryan How, Michael Scott-Nelson, Jarrod Davis, Michael Sewell, Richard Wise and Daniel SchultzI'm not sure why this has been closed, but throwing my hat in for desiring a fix for this. Reading JSON more literally into string types would be a significant improvement to be able to put configs into JSON.
As an example, the WordPress Gutenberg project is moving towards a JSON registration schema for multiple reasons. The list of available category options could and should be tightly limited to the available options. However, due to this bug, we could never enforce a proper category list which effectively breaks TS linting of the file for anyone wanting to use TS when creating Gutenberg blocks or plugins.
Reacted by RichardWLaub, Jarrod Davis, Daniel Schultz and Jacob PadgettI know this issue is closed but having a const json importer would be very useful.
When you inline the json inside a .ts file e.g
const json = {“hello”: “world”}then world is treated like a const, but if you import, then it’s a string.Some typescript way if going from const to strong via a typecase makes sense for those who want existing behavior.
With existing json behavior any times that use discriminated unions break because type comes back as “string”.
Typescript authors, how you do propose I validate json to a type?
Reacted by Daniel Schultz and Lucasmscottnelson commented
on May 13, 2020 More actionsI've been trying to work on a fix for some of these issues here: https://github.com/pabra/json-literal-typer. If your use-case is relatively straightforward (limited special characters, no escape characters in string literals), then it may satisfy some needs. Would love to have this built-in to the language, but hopefully this will be helpful to some in the interim.
I ran into this when one of the functions, to which I'm passing my json into into, is typed.
import myconfig from './config/ some_function(myconfig)I get type errors saying that
some_functionexpects a value to be a string literal, but instead gotten type string.This defeats the purpose of the typing on
some_function. This is not ideal when the json I import is massive (why we move it to another file in the first place). I end up coercing it withas ConfigOptions, but this also defeats the purpose of the type checking since I can now pass in incorrect data to the function.I did come up with a solution, which is to not use .json and rename the file to a .ts. Then, I did a named export on the config, which I typed as the provided
ConfigOptions. This preserves ability to not mess up my values that I pass in.Here's the example:
export const config: GrantOptions = { "defaults": { "protocol": "http", ... } } ... import { config } from './config/grant-config'; app.use(grant(config))Reacted by Phoenix G, Jacob Eggers, Hector Ayala and Daniel SchultzWanted to chime in here to agree with the limitations of the current implementation and provide another use case.
I'm using ajv to create type guards for my types that are powered by a Type-validated-JSONschema. This is really handy because it's possible for ajv to throw type errors if the schema (and therefore the type guard) does not match the Type definition.
This all works fine if defined inline:
export interface Foo { bar: number; }; const validatedFooSchema: JSONSchemaType<Foo> = { type: 'object', properties: { bar: { type: 'integer' }, }, required: ['bar'] }; export const isFoo = ajv.compile(validatedFooSchema);However, I want to define my json schema in an external json file so that I can reference it in other places (e.g. in an OpenAPI spec). I cannot do this with the current TypeScript json import implementation, since this schema is not interpreted as the required string literals but as generic
stringtypes:fooSchema.json
{ "type": "object", "properties": { "bar": { "type": "integer" }, }, "required": ['bar'] }Foo.ts
import fooSchema from '../fooSchema.json'; export interface Foo { bar: number; }; const validatedFooSchema: JSONSchemaType<Foo> = fooSchema; export const isFoo = ajv.compile(validatedFooSchema);^
validatedFooSchemaerrors with:Types of property 'type' are incompatible. Type 'string' is not assignable to type '"object"'Reacted by Sebastian Hiebl, bickelj, Ryan McDonald, Antony74, Christopher Funderburk, Jacob Padgett and Lou HuangThis is a genuine challenge, especially when it comes to working with ajv schemas, but it is not a json import problem. We can trivially convert json to ts by prefixing
export default. Notice the same thing still happens.fooSchema.ts
export default { type: 'object', properties: { bar: { type: 'integer', }, }, required: ['bar'], };Foo.ts
import Ajv, { JSONSchemaType } from 'ajv'; import fooSchema from './fooSchema'; const ajv = new Ajv(); export interface Foo { bar: number; } const validatedFooSchema: JSONSchemaType<Foo> = fooSchema; export const isFoo = ajv.compile(validatedFooSchema);^
validatedFooSchemaerrors with:Types of property 'type' are incompatible. Type 'string' is not assignable to type '"object"'.What a pity we can do things with literal types within .ts files that we can't do between files.
i.e. the following does successfully compile and run:
import Ajv, { JSONSchemaType } from 'ajv'; const ajv = new Ajv(); export interface Foo { bar: number; } const validatedFooSchema: JSONSchemaType<Foo> = { type: 'object', properties: { bar: { type: 'integer', }, }, required: ['bar'], }; export const isFoo = ajv.compile(validatedFooSchema);Reacted by Daniel SchultzAntony74 for your thing, try
export default { type: 'object', properties: { bar: { type: 'integer', }, }, required: ['bar'], } as const
You could do
import { JSONSchemaType } from 'ajv' import { Foo } from 'Foo.ts' export default { /* ... */ } as JSONSchemaType<Foo>
but I suppose that may defeat the point for you.
This issue is specifically about importing
.jsonfiles without modification or additional tooling.Reacted by Antony74, WhereJuly, ryomaejii, Jacob Padgett and Palash BansalMihail Malo (@qm3ster) Yes you're right, the
as constdoes the trick with .ts thanks, making the problem just to do with .json where there is no equivalent trick.(you're also right about
as JSONSchemaTypedefeating the point, which is to check (at compile time) rather than assume that the given schema is compatible with the type Foo).Antony74 it shouldn't assume anything, it would still conflict.
as JSONSchemaType<Foo>doesn't blindly cast conflicting types, onlyas any as JSONSchemaType<Foo>does that.
as const as JSONSchemaType<Foo>may be stronger though, not sure about your case.If you want to typecheck but export the most literal type and not potentially partially type-erased, you could also go with
const x = { /* ... */ } as const const _: JSONSchemaType<Foo> = x export default x
Reacted by Wenchen LiPlease consider implementing loading JSON definitions as const!
This is clearly a missing feature. In our case, configuration options are loaded from a server (they usually don't change daily), so the dev runtime fetches the config and voila. However, to have better string typing, now we must convert the JSON into a fake ts file with a script. Importing JSON resources as const by default would solve this.
Reacted by gregservera, Blynsky Niki, Dois, Eduardo, Geert Fokke, Audun Meek Olsen, Justin (Nat) and Oskar Larsson HögfeldtReacted by gregservera, Eric Berndt and EduardoAny update ?
Reacted by Rubén Sospedra, Audun Meek Olsen, Jiraiya27, Justin (Nat) and Michael AbsolonRunning into this problem - with another use case.
Apart of a larger project I'm generating a json file which is imported into a TS file which re-exports it but with a type being applied. This allows for pre-runtime type checking to stop the execution without having to implement runtime parsing (such as zod). With the bonus of type intellisense.
yhaiovyi-tripactions commented
on Jun 28, 2025 More actionsIt makes absolutely no sense to treat JSON as not a
const, it is aconstby definition.Reacted by David- locked as resolved and limited conversation to collaborators
on Oct 21, 2025
Search Terms
import jsonliteral typespecific typeSuggestion
When importing a JSON file, strings and numbers are typed as
stringandnumberrather than the string or number literals in the file.Also, array literals are imported as
T[]instead of[T1, T2, T3]tuples.Since the JSON is almost an object literal, I believe it makes more sense to type it more specifically.
I have faced most of my issues with the former, but I believe as much information as possible should be provided, which means tuples instead of arrays.
At least the former shouldn't be particularly breaking as the types are replaced by subtypes, but of course can be breaking if
typeofreflection is used to jam less specific types into it.Use Cases
In a number of cases, JSON imported are expected to conform to a schema that includes specific enums. Please see example.
Examples
Currently, this will not compile, because
This forces skipping the type checking of this completely statically available, and valid, object:
const typings = compile(eventSchema as any, 'Event')Checklist
My suggestion meets these guidelines: