Как типизировать JSON-значение с вложенностью объектов и массивов любой глубины?
Объявите один псевдоним типа Json, описывающий любое значение, которое может вернуть JSON.parse.
Требования: покрыть примитивы и null; допускать массив JSON-значений; допускать объект, значения свойств которого — JSON-значения. Вложенность должна работать на любую глубину, без написанного руками ограничения глубины и без отката к any.
type Json = /* ваш код здесь */;
const ok: Json = { a: [1, 'x', { b: [null, true] }] };
Допишите реализацию.
Псевдоним типа может ссылаться на себя, поэтому Json — это рекурсивное объединение: type Json = string | number | boolean | null | Json[] | { [k: string]: Json }. Обе ссылки на себя стоят внутри массива или индексной сигнатуры, что откладывает рекурсию, поэтому компилятор разворачивает её лениво и вложенность работает на любую глубину.
- ✗Считать, что псевдоним типа не может ссылаться на себя
- ✗Тянуться к
anyилиunknownвместо того, чтобы выразить рекурсию напрямую - ✗Протаскивать самодельный счётчик глубины, не нужный лениво разворачиваемому псевдониму
- →Что изменится, если объявить
Jsonкакinterface, а не как псевдоним типа? - →Когда рекурсивный тип упирается в предел глубины инстанцирования компилятора?
Тип-псевдоним в TypeScript может ссылаться на самого себя, если ссылка стоит в отложенной позиции — внутри массива, кортежа, индексной сигнатуры или свойства объекта. Именно это позволяет описать произвольно вложенный JSON одним объявлением.
type Json =
| string
| number
| boolean
| null
| Json[]
| { [key: string]: Json };
const ok: Json = { a: [1, 'x', { b: [null, true] }] };
// @ts-expect-error — функция не является JSON-значением
const bad: Json = { a: () => 1 };
Компилятор не разворачивает Json заранее до фиксированной глубины: он подставляет определение только тогда, когда проверяет конкретное значение, и ровно на ту глубину, которая в этом значении есть. Поэтому счётчик глубины не нужен.