1

I wonder if it is possible to type JSON-strings in Typescript. I would like to have this as feature in the code editor to have (compile time) error checks and autocompletion. I'm not looking for runtime validation.

I thought of something like this (try it yourself with TS Playground ):

export type Json<ContainedType> = string;

export function decodeJson<ContainedType>(
  json: Json<ContainedType>
): ContainedType {
  return JSON.parse(json) as ContainedType;
}

export function encodeJson<ContainedType>(
  value: ContainedType
): Json<ContainedType> {
  return JSON.stringify(value) as Json<ContainedType>;
}

export type ExampleType = {
  example: string;
};

const example: ExampleType = { example: 'value' };

// expected type is Json<ExampleType> but vs code shows string
const encodedExample = encodeJson(example); 

// expected type is ExampleType but vs code shows unknown
const decodedExample = decodeJson(encodedExample); 

Any ideas to get something like this working?

1
  • 1
    JSON is just a string, so no, it's not possible to tell TS that a string matches some structure. Only that it matches given literal. You can probably use branded types or opaque types (the article calls it "flavouring" but it's the same thing) but you also need to be explicit about these strings, you won't get much automation. However, jsonTypeA = jsonTypeB would fail at compilation time if you've personally typed them correctly. Commented Mar 16, 2021 at 8:08

2 Answers 2

1

Guess I should have done a little more research. There is a solution to be found here. Instead of using just a simple string, a dummy field carrying the type information can be added to the type (to keep Typescript from reducing the type to string):

export type Json<ContainedType> =  string & {__JSON__: ContainedType};

Have a look at the playground here. If you want to you can even add the types to the existing JSON functions:

declare const JSON: {
  parse: <T>(str: JSON<T>) => T;
  stringify: <T>(obj: T) => JSON<T>;
};
Sign up to request clarification or add additional context in comments.

Comments

0

A string is a string, Typescript can't really distinguish between them. You could use Literal Types (read more about them in the handbook), but this doesn't really make sense in the context of JSON processing.

So encoding a JSON will always return a string. But if you know what the content of a JSON string has to be, you can just use generics to type out the decoded JSON (This is also done in ajax libraries, such as axios.io).

To take your example, do something like this:

type ExampleType = {
  example: string;
};

const example: ExampleType = { example: 'value' };

function decodeJson<T>(str: string) {
  return JSON.parse(str) as T;
}

function encodeJson<T>(json: T) {
  return JSON.stringify(json);
}

// expected type is string
const encodedExample = encodeJson(example); 

// expected type is ExampleType
const decodedExample = decodeJson<ExampleType>(encodedExample); 

Check out this playground for a live example.

Mind you that this hard casts the return variable of decodeJson, so you have to be sure that this is the type that will be returned from that string represented JSON.

Also, to give a bit more explanation on why TypeScript cannot compile check this: JSON.stringify and JSON.parse are in itself basically generic functions during runtime. The first one takes any valid JSON and returns a string, the latter one any string and returns a JSON or throws an error if it is not a JSON. Since these are runtime operations, TypeScript cannot possible know what values will be processed.

Using a hard cast works around the issue but be careful with that. Hard casting can introduce bugs if the runtime format does not comply with the compile type.

3 Comments

I'm aware that i could just cast the result of JSON.parse ;-) . I'm not gaining much by calling decodeJson<ExampleType>(x) instead of JSON.pase(x) as ExampleType. I want to move the definition of the deserialized type away from the function call. This would come in handy when typing responses for a server that encapsulates JSON strings.
Well this is not possible. How would TypeScript know about the result of a server call during compilation time?
The result type would be in the TypeScript code, but not at the function call. It should be encoded in the type of the function parameter. I meanwhile found a solution and posted it as an answer. Sorry for wasting your time :-\

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.