SoFunction
Updated on 2025-03-10

Deriving the blind box type details of existing variables using typescript

Migrate blind boxes

When we convert Typescript from JavaScript with one click, any is the most convenient way to do it. It is not friendly to maintenance (although it can be run). At the same time, each variable is a blind box for us. What type is it?

Type Derivation

Derivation of basic types

The type derivation of basic data types is quite simple

let a = 1;
type A = typeof a; // number;

let b = '2'
type B = typeof b; // string;

let c;
type C = typeof c; // undefined;

A typeof can deduce the original value type!

What are the basic data types we have sorted out?

// That's right, 7 data typestype Base = string | number | boolean | null | undefined | symbol | bigint;

Derivation of objects

Here we will implement how to deduce ordinary objects

let obj = { a: 1, b: '2', c: true, d: null };
type Obj = typeof obj;
// { a: number; b: string; c: boolean; d: null; }

That's right, that's that simple!

Derivation of arrays

Why do the above objects besides the array? Because arrays are special in typescript, they can be declared with both original ancestors and arrays.

It can also be said that the array contains the Yuanzu

type isContain = [1, 2] extends Array<number> ? true : false; // true

Try to continue to implement derivation through typeof

let arr = [1, '2', null, false];
type Arr = typeof arr; // (string | number | boolean | null)[]    ???
type Arr1 = typeof arr[0] // string | number | boolean | null    ???

Well, what I got was an array of union types. Is it different from what we expected?

We define an array but do not declare the type. For arrays, the default is Array. Because the values ​​of number, string, null, and boolean are constantly filled, it finally becomes Array<string | number | boolean | null>, and every element is a union type

Reorganize, what do we need?

[1, '2', null, false] -> [number, string, null, boolean]

What we want is Yuanzu[number, string, null, boolean], not arrayArray<string | number | boolean | null>

To sort out todo, what we need is:

  • Fixed-length array type
  • Each element has an independent type
let arr = [1, '2', null, false] as const;
type Arr = typeof arr; // readonly [1, '2', null, false]
type Arr1 = typeof arr[0] // 1

The first todo is implemented, and the second one is a bit like, but it is not right. What we want is the data type, not a specific value

Implement a generic of a conversion type

What we want is 1 to number, 'A' to string, just list all the basic types and convert them.

type GetType<T> = T extends string ? string :
    T extends number ? number :
    T extends boolean? boolean :
    T extends null ? null :
    T extends undefined ? undefined :
    T extends symbol ? symbol :
    T extends bigint ? bigint : T;

type Arr1 = typeof arr[0] // number
type Arr2 = typeof arr[1] // string

Then you can realize the type conversion of the entire array by traversing the original one again.

type TransArr<T extends Array<unknown>,
    R extends unknown[] = []> = {
        'loop': TransArr<T,
            [...R,
                T extends Base ?
                    GetType<T[R['length']]>: T[R['length']]
            ]
        >,
        'result': R,
}[T['length'] extends R['length'] ? 'result': 'loop'];

let arr = [1, '2', null, false] as const;
type Arr = typeof arr;
type ArrType = TransArr<Arr>; // [number, string, null, boolean]

Derivation of functions

The derivation of functions is actually not necessary. Why do you say that, function parameters and value types are uncontrollable except for individual operators or explicit types

If (a, b) => a * b , the return value must be number

If (a) => (a), the return value must be Promise

let fn1 = (a, b) => a * b;
type Fn1 = typeof fn1; // (a: any, b: any) => number

function fn2(a) {
    return (a);
}
type Fn2 = typeof fn2; // (a: any) => Promise<any>

Most of the results obtained by function after passing through typeof are

(a: any, b: any, ...) => any;

This type can limit the number of functions with more parameters

function fn3(a, b, c) {
    return a + b + c;
}
function fn4(d) {
    return d + 1;
}
type Fn3 = typeof fn3; // (a: any, b: any, c: any) => any
type Fn4 = typeof fn4; // (d: any) => any

type F3_4 = Fn3 extends Fn4 ? true : false; // false
type F4_3 = Fn4 extends Fn3 ? true : false; // true

In other words, functions with more parameters always contain functions with fewer parameters.

According to the above judgment, we can use this to realize the function's judgment

type isFunc<T> = (() => any) extends T ? true : false;

Improve the guidance

  • The basic type returns the type directly
  • TransArr generic to array
  • The function directly returns the value of typeof
  • It uses keyof to traverse objects.
type Trans<T> = T extends Base
    ? GetType<T> : T extends Array<unknown>
    ? TransArr<T> : isFunc<T> extends true
    ? T : {
        [key in keyof T]: T[key] extends Base
            ? GetType<T[key]> : T[key] extends Array<unknown>
            ? TransArr<T[key]> : Trans<T[key]>;
        };

test

let a1 = 1;
type test1 = Trans<typeof a1>; // number
let a2 = '2';
type test2 = Trans<typeof a2>; // string
let a3 = [1, '2', true, '3', 4] as const;
type test3 = TransArr<typeof a3>;
// [number, string, boolean, string, number]
let a4 = {
    a: 1,
    b: true,
    c: {
        a: 1,
        b: [1, '2']
    },
    d: [true, null]
} as const;
type test4 = Trans<typeof a4>;
// {
//     readonly a: number;
//     readonly b: boolean;
//     readonly c: {
//         readonly a: number;
//         readonly b: readonly [number, string];
//     };
//     readonly d: readonly [boolean, null];
// }
let a5 = {
    a: [
        {
            b: [
                { c: 1 }
            ]
        }
    ]
} as const;
type test5 = Trans<typeof a5>;
// {
//     readonly a: readonly [{
//         readonly b: readonly [{
//             readonly c: number;
//         }];
//     }];
// }
let a6 = (a, b, c) => a + b + c;
type test6 = Trans<typeof a6>;
// (a: any, b: any, c: any) => any
let a7 = [
    function fn() {
        return 1;
    },
    (a, b) => a * b,
    (a) => (a)
] as const;
type test7 = TransArr<typeof a7>;
// [() => number, (a: any, b: any) => number, (a: any) => Promise<any>]
let a8 = {
    a: 1,
    b: [true, null],
    c: [() => void, (a, b) => a],
    d: {
        e: [
            (a, b) => null,
            {
                f: [1]
            }
        ]
    }
} as const;
type test8 = Trans<typeof a8>;
// {
//     readonly a: number;
//     readonly b: readonly [boolean, null];
//     readonly c: readonly [() => undefined, (a: any, b: any) => any];
//     readonly d: {
//         readonly e: readonly [(a: any, b: any) => null, {
//             readonly f: readonly [number];
//         }];
//     };
// }

This is the end of this article about using typescript to deduce blind box types with existing variables. For more related typescript blind box types, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!