-1

How can I add types for a nested object, that looks like this.

const theme: DefaultTheme = {
    color: {
        primary: '#5039E7',
        secondary: '#372E4B',
        heading: '#4D5062',
    },
    font: {
        size: {
            extraLarge: '7.2rem',
            large: '5.2rem',
            medium: '3.6rem',
            small: '2.4rem',
            hyperlink: '1.8rem',
            paragraph: '1.6rem',
        },
        family: 'sans-serif',
    },
 };

I did it like so, but feel like this is not a good aproach to add types to an Object

interface DefaultTheme {
    color: {
        primary: string;
        secondary: string;
        heading: string;
    };
    font: {
       size: {
         extraLarge: string;
         large: string;
         medium: string;
         small: string;
         hyperlink: string;
         paragraph: string;
        };
        family: string;
    };
}
Vna
  • 532
  • 6
  • 19
Andrew
  • 1,507
  • 1
  • 22
  • 42
  • Why do you think it's "not a good approach"? Answering that could lead you to solutions that you like better. – T.J. Crowder Apr 27 '21 at 12:15
  • it is very verbose, i'm not very familiar with all possibilities of a TypeScript, so i thought maybe some one know's more elegant solution – Andrew Apr 27 '21 at 12:17

3 Answers3

1

I think creating models for nested objects also and giving the type as those models is a clean approach

interface DefaultTheme {
    color: Color;
    font: Font;
}

interface Color {
    primary: string;
    secondary: string;
    heading: string;
}

interface FontSize {
    extraLarge: string;
    large: string;
    medium: string;
    small: string;
    hyperlink: string;
    paragraph: string;
}

interface Font {
    size: FontSize;
    family: string;
}

Vna
  • 532
  • 6
  • 19
1

Your approach is totally fine. But here is my approach.


 interface ColorInterface<T> {
     primary: T;
     secondary: T;
     heading: T;
 }

 interface SizeInterface<T> {
    extraLarge: T;
    large: T;
    medium: T;
    small: T;
    hyperlink: T;
    paragraph: T;
 }

 interface FontInterface<T> {
     size: SizeInterface<T>,
     family: T;
 }

 interface DefaultThemeInterface<T> {
     color: ColorInterface<T>,
     font: FontInterface<T>
 }




const theme: DefaultThemeInterface<string> = {
    color: {
        primary: '#5039E7',
        secondary: '#372E4B',
        heading: '#4D5062',
    },
    font: {
        size: {
            extraLarge: '7.2rem',
            large: '5.2rem',
            medium: '3.6rem',
            small: '2.4rem',
            hyperlink: '1.8rem',
            paragraph: '1.6rem',
        },
        family: 'sans-serif',
    },
 };
Sifat Haque
  • 5,357
  • 1
  • 16
  • 23
  • Thanks for your answer splitting each object into a different Interface is a pretty good idea. What do you suggest to use for such an object `Interface` or rather `type`? – Andrew Apr 27 '21 at 15:10
  • for now you can use type but if you need to extend them then go for interface. – Sifat Haque Apr 27 '21 at 15:15
1

Here you have some weird TypeScript validator:

type HexNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type HexString = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
type StringNumber<T extends number> = `${T}`
type HEX = HexNumber | StringNumber<HexNumber> | HexString;

type Check<T extends string, Cache extends readonly string[] = []> =
    T extends `${infer A}${infer Rest}`
    ? A extends HEX
    ? Check<Rest, [...Cache, A]> : A extends ''
    ? 1 : 2 : T extends '' ? Cache extends { length: 6 }
    ? Cache : never : never;

type Elem = string;

type Mapper<
    Arr extends ReadonlyArray<Elem>,
    Result extends string = ''
    > = Arr extends []
    ? Result
    : Arr extends [infer H]
    ? H extends Elem
    ? `${Result}${H}`
    : never
    : Arr extends readonly [infer H, ...infer Tail]
    ? Tail extends ReadonlyArray<Elem>
    ? H extends Elem
    ? Mapper<Tail, `${Result}${H}`>
    : never
    : never
    : never;


type Result = Mapper<Check<'abcdef'>> // allow
type Result2 = Mapper<Check<'00cdef'>> // allow
type Result3 = Mapper<Check<'z0cdef'>> // not allow
type Result4 = Mapper<Check<'00cdem'>> // not allow
type Result5 = Mapper<Check<'aaaaa'>> // to few arguments

type Color<S extends string> = {
    primary: S;
    secondary: S;
    heading: S;
}

type Size<T extends string> = {
    extraLarge: T;
    large: T;
    medium: T;
    small: T;
    hyperlink: T;
    paragraph: T;
}

interface DefaultTheme<C extends string, S extends string> {
    color: Color<C>;
    font: {
        size: Size<S>;
        family: string;
    };
}

type RemoveHash<T extends string> =
    T extends `${infer Hash}${infer Rest}`
    ? Hash extends '#' ? Rest
    : never
    : never;

const theme = {
    color: {
        primary: '#5039E7',
        secondary: '#372E4B',
        heading: '#4D5062',
    },
    font: {
        size: {
            extraLarge: '7.2rem',
            large: '5.2rem',
            medium: '3.6rem',
            small: '2.4rem',
            hyperlink: '1.8rem',
            paragraph: '1.6rem',
        },
        family: 'sans-serif',
    },
} as const;


type HEXValidation<T extends string> = Check<RemoveHash<T>> extends string[] ? Mapper<Check<RemoveHash<T>>> extends RemoveHash<T> ? [] : [never] : [never]

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

type IsRem<T extends string> = T extends `${number}.${number}rem` ? 1 : 2;

type UNITvalidation<T> = IsUnion<T> extends true ? [never] : [];



declare function validator<T extends string, S extends string>
    (config: DefaultTheme<T, S>, ...flag:[...HEXValidation<T>, ...UNITvalidation<IsRem<S>>]):void;

const result = validator(theme) // ok

/**
 * Tests
 */
const result2=validator({
    ...theme,
    font:{
        ...theme.font,
        size:{
            ...theme.font.size,
             paragraph: '1.6rm', // <---error because of type, should be rem
        }

    }
})

const result3=validator({
    ...theme,
    color:{
        ...theme.color,
        primary: '#5039E-', // <--- error becaue of invalid HEX
    }
})

Playground

Here you can find explanation to HEX validation