Open streamich opened 5 years ago
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)
Search Terms
Suggestion
Type annotation for JSON in a string.
Use Cases
Let's say you have a string which contains valid JSON object, like so:
How can you type annotate the
json
variable? Currently, you can mark it as astring
:Instead there could be some TypeScript language feature that helps with typing JSON in a string more precisely, for example:
Examples
Specify that string contains valid JSON.
Add typings to an HTTP response body.
Add type safety to
JSON.parse()
method.JSON cannot contain complex types.
Doubly serialized JSON.
Get type of serialized JSON string using
jsontype
keyword.Specify that variable is JSON-serializable.
Checklist
My suggestion meets these guidelines:
Syntax Alternatives