Type annotation for JSON in a string.
Let's say you have a string which contains valid JSON object, like so:
const json = '{"hello": "world"}';
How can you type annotate the json variable? Currently, you can mark it as a string:
const json: string = '{"hello": "world"}';
Instead there could be some TypeScript language feature that helps with typing JSON in a string more precisely, for example:
const json: JSON {hello: string} = '{"hello": "world"}';
Specify that string contains valid JSON.
let json: JSON any;
let json: JSON; // shorthand
Add typings to an HTTP response body.
let responseBody: JSON {ping: 'pong'} = '{"ping": "pong"}';
Add type safety to JSON.parse() method.
let responseBody: JSON {ping: 'pong'} = '{"ping": "pong"}';
let {ping} = JSON.parse(responseBody);
typeof ping // 'pong'
JSON cannot contain complex types.
type Stats = JSON {mtime: Date}; // Error: Date is not a valid JSON type.
Doubly serialized JSON.
let response: JSON {body: string} = '{"body": "{\"userId\": 123}"}';
let fetchUserResponse: JSON {body: JSON {userId: number}} = response;
Get type of serialized JSON string using jsontype keyword.
type Response = JSON {body: string, headers: object};
type ResponseJson = jsontype Response; // {body: string, headers: object}
type ResponseBody = ResponseJson['body']; // string
type ResponseBody = (jsontype Response)['body']; // string
Specify that variable is JSON-serializable.
let serializable: jsontype JSON = {hello: 'world'};
JSON.serialize(serializable); // OK
let nonserializable: object = {hello: 'world'};
JSON.serialize(nonserializable); // Error: 'nonserializable' might not be serializable.
My suggestion meets these guidelines:
type ResponseRaw = JSON {ping: 'pong'};
type ResponseRaw = json {ping: 'pong'};
type ResponseRaw = string {ping: 'pong'};
type ResponseRaw = json_string {ping: 'pong'};
type ResponseRaw = JSON<{ping: 'pong'}>;
type ResponseRaw = JSON({ping: 'pong'});
type Response = jsontype Response; // {ping: 'pong'}
type Response = typeof Response; // {ping: 'pong'}
type Response = parsed(Response); // {ping: 'pong'}
It seems like you want, generally, refinements on string types. In the vein of #6579 (for specifically regex refinements) or #4895 (for arbitrary nominal refinements).
@weswigham refinements on string type, yes, but this proposal deals specifically with JSON, which is a common use case and—I believe—specific enough that it could actually be implemented.
What are the use cases for writing JSON strings in code instead of writing them as parsed literals?
@RyanCavanaugh I have plenty of mock data for tests as JSON in strings, when you receive response from and API it could be JSON in a string, when you read from a file it could be .json. I'm sure there a re plenty more examples.
Doubly, triply, etc. serialized JSON is another example.
'{"body": "{\"userId\": 123}"}' // JSON {body: JSON {userId: number}}
What I mean is, if you're writing the code, why are you writing them in the error-prone "{ 'x': 'y'}" form instead of the easier { x: 'y' } form?
@RyanCavanaugh I am not, but sometimes you receive your data in that form and you have to deal with it. For example, here is a typical AWS SQS response example:
{
"Messages": [
{
"Body": "{\n \"Message\" : \"{\\\"assetId\\\":14,\\\"status\\\":\\\"Uploading\\\",\\\"updatedAt\\\":\\\"2018-10-16T08:47:43.538Z\\\"}\",\n }"
}
]
}
(I have removed some fields for brevity. Also, I hope all the escapings are correct. :) )
The above is basically dobly-serialized JSON in Messages[0].Body field. I have no control of this format, but I would like to type annotate it somehow. For example it could be done like so:
interface Response {
Messages: ({
Body: JSON {
Message: JSON {
assetId: number;
status: 'Queued' | 'Uploading' | 'Error' | 'Done';
updatedAt: string;
}
}
})[];
}
sometimes you receive your data in that form
Makes sense - but in that case, we can't really do any valuable typechecking of that JSON at compile-time. Or are you saying you're copying the JSON responses into your test files? Just trying to understand
... we can't really do any valuable typechecking of that JSON at compile-time.
Sure, but code can be annotated at dev time so developer can get all the code completion and error messages that are obvious from static code analysis. For example:
JSON.parse(JSON.parse(JSON.parse(message).Body).Message).assetId; // OK
JSON.parse(JSON.parse(JSON.parse(message).Body).Message).oops; // Error: ...
Or are you saying you're copying the JSON responses into your test files?
Yes.
So you're saying it'd be useful coupled with a JSON.parse overload along the lines of
declare function parse<T>(string: JSON T): T;
@weswigham Exactly!
interface GlobalJSON {
parse: <T>(str: JSON T) => T;
stringify: <T>(obj: jsontype T) => T;
}
Along the lines of what people have said in #4895, you can get pretty close with branded strings today:
type JSONString<T> = string & { " __JSONBrand": T };
function parse<T>(str: JSONString<T>): T { return JSON.parse(str as string) as any; };
let responseBody = '{"ping": "pong"}' as JSONString<{ping: 'pong'}>;
parse(responseBody).ping; // OK
there's no automatic creation of them and no automatic validation that your string actually meets the constraint you want the type to imply, but you _can_ flow the type around, at least.
@weswigham How would you annotate JSON.stringify method using branded strings?
function stringify<T>(obj: T): JSON<T> { return JSON.stringify(obj); }
OK, if anyone is interested, here is what I did:
type JSON<T> = string & {__JSON__: T};
declare const JSON: {
parse: <T>(str: JSON<T>) => T;
stringify: <T>(obj: T) => JSON<T>;
};
Autocompletion works:

Autocompletion for above mentioned example works, too:

BTW, create this tiny NPM package if anyone needs branded JSON strings:
It is faster to use JSON.parse of a string literal than to use a JSON object literal:
https://v8.dev/blog/cost-of-javascript-2019#json
So this feature is now a bit more useful (although it is better if the compiler will generate the JSON.parse itself when it sees a JSON literal)
Most helpful comment
BTW, create this tiny NPM package if anyone needs branded JSON strings:
https://github.com/streamich/ts-brand-json