What is the best way to include type within a nested object?

How should I properly define types for a nested object structured like the example below?

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 attempted to define types using an interface as shown below, however, I am unsure if this is the most effective way 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;
    };
}

Answer №1

Your method of approach is acceptable. However, I would like to share my perspective.


 interface ColorScheme<T> {
     primaryColor: T;
     secondaryColor: T;
     headingColor: T;
 }

 interface SizeScheme<T> {
    extraLarge: T;
    large: T;
    medium: T;
    small: T;
    hyperlinkSize: T;
    paragraphSize: T;
 }

 interface FontScheme<T> {
     size: SizeScheme<T>,
     family: T;
 }

 interface CustomTheme<T> {
     color: ColorScheme<T>,
     font: FontScheme<T>
 }




const customTheme: CustomTheme<string> = {
    color: {
        primaryColor: '#5039E7',
        secondaryColor: '#372E4B',
        headingColor: '#4D5062',
    },
    font: {
        size: {
            extraLarge: '7.2rem',
            large: '5.2rem',
            medium: '3.6rem',
            small: '2.4rem',
            hyperlinkSize: '1.8rem',
            paragraphSize: '1.6rem',
        },
        family: 'sans-serif',
    },
 };

Answer №2

Designing models for nested objects and defining their types within those models is a neat way to structure code

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;
}

Answer №3

Here is an interesting 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'>> // too 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 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) // okay

/**
 * 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 because of invalid HEX
    }
})

Playground

Explore more about HEX validation here

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Developing a custom React hook that utilizes the useContext functions

When attempting to utilize a function within a custom hook, I encounter the following error message: Error: tglCartao is not defined The custom hook code in UseCartao.tsx is as follows: export interface ICartaoContext { idToggleKey : string; ...

The use of the .reset() function in typescript to clear form data may lead to unexpected

I've been trying to use document.getelementbyID().reset(); to reset form values, but I keep running into an error in TypeScript. Property 'reset' does not exist on type 'HTMLElement'. Here's how I implemented it: const resetB ...

Send empty object using FormBuilder

When retrieving an object from a backend API service like this: data: {firstName:'pepe',lastName:'test', address = {street: 'Cervantes', city:'Villajoyosa'} } or data: {firstName:'pepe',lastName:'test ...

Is there a way to modify the style when a different rarity is selected in Next.JS?

Is there a way to change the style depending on the rarity selected? I am currently developing a game that assigns a random rarity upon website loading, and I am looking to customize the color of each rarity. Here is how it appears at the moment: https:/ ...

Navigating with Angular 2: Redirecting Azure AD login URI

I am currently exploring Azure AD integration for logging into an Angular 2 application. The Github link provided below demonstrates a simple method to log in without the use of any authentication libraries. Sign in via Microsoft Graph using Typescript an ...

Angular's custom validator consistently returns a null value

I need help with validating the uniqueness of a username field in a form where an administrator can create a new user. I have implemented a uniqueUserNameValidator function for this purpose, but it always returns null. I suspect that the issue lies in the ...

Leverage the TypeScript compiler's output from a .NET library within a Blazor application by referencing it

I am currently facing an issue with three different levels: Main Issue: I have developed a Blazor WebAssembly (WASM) application that requires JavaScript, but I prefer to use TypeScript. To address this, I have added a tsconfig file and the TypeScript cod ...

I want to modify a class in Angular 8 by selecting an element using its ID and adjust the styling of a div

Is it possible to dynamically add and remove classes using getElementById in Angular 8? I need to switch from 'col-md-12' to 'col-md-6' when a user clicks on the details icon. I also want to modify the style of another div by setting d ...

Guide to crafting a reply using nestjs exception filters with nestfastify

I'm facing a challenge with writing custom HTTP responses from NestJS exception filters. Currently, I am using the Nest Fastify framework instead of Express. I have created custom exception filters to handle UserNotFoundException in the following mann ...

Typescript - any of the types imported from a module

Currently, my code looks like this: import * as Types from '../schema/types'; and I'm looking to achieve something along the lines of: let a: Types; This would signify that a should be one of the various types exported from the file types. ...

The json.stringify method is inserting additional backslashes into the response sent by res.send()

My API needs to provide the following data in its response. { users: 'All users are as follows: [{id: 1}, {id: 2}]'} The response should be a JSON object with one key value being a JSON array. However, the JSON array is converted into a string b ...

Once I introduce ngModel into mat-checkbox, the functionality of 'checked' stops working

When I add an ngModel to my mat-checkbox, the checked = "checked" functionality stops working as expected. The following code will work: <mat-checkbox name="BlackBeard" ngModel checked = "checked"> Zehahaha? </mat-checkbox> However, the foll ...

Discovering routes in Angular2

I'm attempting to replicate something similar to this example here: AngularJS show div based on url/condition <span id="page-locator" *ngIf="location.path() == '/overview'">test</span> Unfortunately, it's not working as ex ...

Mastering the art of leveraging generics in conjunction with ngrx actions

Currently, I am in the process of developing an Angular 7 application that utilizes the NGRX store. In our application, we have numerous entities, each with its own dedicated list view. I decided to explore using generics with the NGRX store to avoid writi ...

Encountering issues with accessing a variable before its initialization in the getServerSideProps function in

Currently, I am working on setting up Firebase and configuring the APIs and functions to retrieve necessary data in my firebase.tsx file. Afterwards, I import them into my pages/index.tsx file but I am encountering an issue where I cannot access exports af ...

What could be causing the cyclic dependency problem after upgrading to Angular 9?

I am experiencing an issue with a specific file containing the following code: import { Injectable } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; import { isNumber } from 'lodash'; import { Confir ...

Having trouble with JavaScript's Date.getUTCMilliSeconds() function?

I have a straightforward question for you. Take a look at this Angular App and try to create a new date, then print the number of UTC milliseconds of that date in the console. Can you figure out why it is returning zero? ...

Utilizing an Angular Service within the main.ts script

My main.ts file currently has the following code snippet: declare const require; const translations = require("raw-loader!./locale/messages.de.xlf"); platformBrowserDynamic().bootstrapModule(AppModule, { providers: [ { provide: TRANSLATIONS, useVa ...

Is it feasible to utilize the redirectTo function alongside the children parameter?

It's important to note that in the latest version of @angular/router 3.0.0-rc.1, you are unable to use the redirectTo parameter if you are also utilizing the children parameter. In some scenarios, such as my own, there may be a need for this function ...

Implementing an extended interface as an argument in a function

Here is the code snippet for analysis: interface IUserData { FirstName: string, LastName: string, Email: string, Password: string } interface IState extends IUserData { isSuccess: boolean } const state: IState = { FirstName: &apo ...