I stumbled upon the following code in production:
type JSONValue = Record<string, any>;
This sort of does the job, but any
is a fruit of a poisonous tree: it should have had a much more ominous name like cheating
or TypeSystemHole
.
Here’s what I mean:
type JSONValue = Record<string, any>; // looks legit const x: JSONValue = { error: new Error("boo!")}; // hm... are classes allowed in JSON? const y: number = x.error; // trust me, this compiles; and y is not a number console.log(y);
Can we define JSON in some better way?
A naïve attempt fails: a type cannot reference itself directly.
// Error: Type alias 'JSONValue' circularly references itself. type JSONValue = null | string | number | JSONValue[] | Record<string, JSONValue>; // this does not compile
Some sources (e.g. this SO answer) suggest that Typescript types cannot be recursive at all, but it is not true.
There are at least two possibilities for recursion with types: recursive properties and referencing yet undefined types:
// recursive property type Tree = { value: number; children: Tree[]; } // referencing yet undefined type type Data = number | string | DataArray; type DataArray = Data[];
The following code defines strongly typed JSON structure without resorting to any
or unknown
:
// helper type type JSONPrimitive = string | number | boolean | null; // main type; use JSONValue to describe valid JSON values type JSONValue = JSONPrimitive | JSONArray | JSONObject; // another helper type type JSONArray = JSONValue[]; // yet another helper type type JSONObject = { [key: string]: JSONValue; }; // some examples const j1: JSONValue = null; const j2: JSONValue = 10; const j3: JSONValue = false; const j4: JSONValue = "foobar"; const j5: JSONValue = [1, 2, "3"]; const j6: JSONValue = { x: null, y: false, z: [1,2,3], t: { foo: 42, bar: null }}; // some counter-examples const not_j1 : JSONValue = { x: new Error("boo")}; // this fails to compile
In conclusion, you don’t need to resort to any
to define a recursive type like JSONValue
: use recursive types instead. Using any
opens a gaping whole in the type system and is therefore, not recommended.