Converting a dynamic JSON object into a generic type in TypeScript

I need assistance with converting a JSON object into the equivalent generic type in TypeScript.

The JSON object I have contains dynamic keys such as applications and permissions. The keys inside applications, like application_management and user_management, can also be dynamic. Similarly, the keys inside permissions, such as create_application, delete_application, update_application, etc., can be dynamic as well.

I attempted to accomplish this using an interface but unfortunately faced challenges. Could someone please help me resolve this issue?

{
            "id": 1,
            "isSuperAdmin": true,
            "firstName": "Oleksii",
            "lastName": & "Michael",
            "email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="452429203d3505352a2c2b31773220276b262a28">[email protected]</a>",
            "gender": "male"",
            "dob": "1990-01-01",
            "photo": null,
            "status": "ACTIVE",
            "roles": [
                {
                    "id": 1,
                    "slug": "Head of Department-(Account Management-Network-P2W North America)"
                },
                {
                    "id": 2,
                    "slug": "Team Lead-(Account Management-Network-P2W North America)"
                },
                {
                    "id": 3,
                    "slug": "Employee-(Account Management-Network-P2W North America)"
                }
            ],
            "applications": {
                "application_management": {
                    "id": 41,
                    "slug": "application_management",
                    "appName": "Application Management",
                    "status": "ACTIVE",
                    "appType": "CODED",
                    "appUrl": "application-management",
                    "appIcon": "http://api.chromosome-studio.com/uploads/applications/application.png",
                    "permissions": {
                        "delete_application": {
                            "id": 3,
                            "action": "Delete Application",
                            "slug": "delete_application",
                            "level": "all"
                        },
                        "update_application": {
                            "id": 2,
                            "action": "Update Application",
                            "slug": "update_application",
                            "level": "all"
                        },
                        "create_application": {
                            "id": 1,
                            "action": "Create Application",
                            "slug": "create_application",
                            "level": "all"
                        }
                    }
                },
                


                "user_management": {
                    "id": 42,
                    "slug": "user_management",
                    "appName": "User Management",
                    "status": "ACTIVE",
                    "appType": "CODED",
                    "appUrl": "user-management",
                    "appIcon": "http://api.chromosome-studio.com/uploads/applications/users.png",
                    "permissions": {
                        "create_user": {
                            "id": 4,
                            "action": "Create User",
                            "slug": "create_user",
                            "level": "all"
                        }
                    }
                }
            }

Answer №1

Oh no, another Romain beat me to it. However, my solution offers a slight variation

type Role = { id: number; slug: string };
type ObjectStatus = 'ACTIVE' | 'INACTIVE'; 
type AppType = 'CODED' | 'DYNAMIC';
type PermissionLevel = 'all' | 'admin' | 'guest'; 

type ApplicationsType = 'user' | 'application';
type CrudTypes = 'delete' | 'update' | 'create';
type PermissionKeys = `${CrudTypes}_application`;

type Permission<NAME extends ApplicationsType, CRUD extends CrudTypes = CrudTypes> = {
    id: number;
    action: `${Capitalize<CRUD>} ${Capitalize<NAME>}`;
    slug: `${CRUD}_${NAME}`;
    level: PermissionLevel;
};

type Application<NAME extends ApplicationsType> = {
    id: number;
    slug: `${NAME}_management`;
    appName: `${Capitalize<NAME>} Management`;
    status: ObjectStatus;
    appType: AppType;
    appUrl: `${NAME}-management`;
    permissions: { [key in PermissionKeys]?: Permission<NAME> };

    [key: string]: unknown;
};

type ApplicationList = { [key in ApplicationsType as `${key}_management`]?: Application<key> };

type User = {
    id: 1;
    isSuperAdmin: true;
    firstName: string;
    lastName: string;
    email: string;
    gender: string | null;
    dob: string | null;
    photo: string | null;
    status: ObjectStatus;
    roles: Array<Role>;
    applications: ApplicationList;
};

Upon applying this type to your input, I encountered compilation errors on

  • application.user_management.appName, which should be 'User Management'
  • application.user_management.permission.create_application.action, expected to be 'Create User'
  • application.user_management.permission.create_application.slug, anticipated to be 'create_user'

A minor update was made to refine the typing of permission list due to imperfect typing issues

type PermissionList<NAME extends ApplicationsType> = { [key in CrudTypes as `${key}_${NAME}`]?: Permission<NAME, key> };

Then make the replacement in Application :

permissions: PermissionList<NAME>;

This resulted in an additional error on

  • application.user_management.permission.create_application, now expected to be 'create_user', allowing only 'Create' as a crud action.

playground

Answer №2

Below is a defined type for your JSON object:

    type JsonObjectType = {
        id: number;
        isAdmin: boolean;
        firstName: string;
        lastName: string;
        email: string;
        gender: string | null;
        dob: string | null;
        photo: string | null;
        status: "ACTIVE";
        roles: {
            id: number;
            slug: string;
        }[];
        applications: Record<string, {
            id: number;
            slug: string;
            appName: string;
            status: "ACTIVE";
            appType: "CODED";
            appUrl: string;
            appIcon: string;
            permissions: Record<string, {
                id: number;
                action: string;
                slug: string;
                level: "all";
            }>;
        }>
    };

The usage of Record<KeyType,ValueType> allows for dynamic properties with string keys and complex typed values.

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

Unable to configure unit tests for Vue project using Typescript due to TypeError: Unable to destructure property `polyfills` of 'undefined' or 'null'

I've been working on adding unit tests for an existing Vue project that uses Typescript. I followed the guidelines provided by vue-test-utils for using Typescript, but when I ran the test, I encountered an error message stating: TypeError: Cannot d ...

The Next.js React framework seems to be having trouble reading user input from a

I'm encountering an issue when attempting to save form email/password registration using Next.js as it is throwing an error. import {useState} from 'react' type Props = { label: string placeholder?: string onChange: () => void na ...

Waiting for Angular's For loop to complete

Recently, I encountered a situation where I needed to format the parameters and submit them to an API using some code. The code involved iterating through performance criteria, performance indicators, and target details to create new objects and push them ...

Steps to filter types by a singular property assessment

export type HalfSpin = { halfspin: string } export type FullSpin = { fullspin: string } export type SpinType = | HalfSpin | FullSpin export function isHalfSpin(_: SpinType) ...

Definition of Stencil Component Method

I'm encountering an issue while developing a stencil.js web component. The error I'm facing is: (index):28 Uncaught TypeError: comp.hideDataPanel is not a function at HTMLDocument. ((index):28) My goal is to integrate my stencil component i ...

Building a versatile component library for Next.js using TypeScript and Tailwind CSS: Step-by-step guide

Lately, I've been utilizing Next.js and crafting components such as buttons, inputs, and cards with Tailwind CSS for my various projects. However, the repetitive task of rewriting these components from scratch for each new project has become quite tir ...

Trying to filter an array of number|undefined in TypeScript to only include numbers is not identified as an array of numbers

Below is the code snippet: type TEntity = Array<{ size?: number }> const someVar: TEntity = //@ts-ignore getFromSomewhere() function isNumber(input: any): input is number { return !isNaN(Number(input)) } const sizes1: number[] = so ...

The ngFor directive in Angular should be used with the Filter pipe to ensure that

Having a Filter implemented in my Angular Project that fetches data from Firebase. The current status in the Filter is as follows: Name 1: Lea Muster Name 2: Bruno Mustermann Name 3: Lea Muster Name 4: Gabriela Musterfrau The goal is to show duplicate e ...

A Project built with React and TypeScript that includes code written in JavaScript file

Is it an issue when building a React project with Typescript that includes only a few JS components when moving to production? ...

The application within the Main Module is not being acknowledged by the other components within the module

I am facing an issue with my AngularJS application where the directive I created within the 'FormTest' module is not recognizing the variable 'app' even though it is defined within the same module. The error message I receive is TS2304 ...

Tips for preserving @typedef during the TypeScript to JavaScript transpilation process

I have a block of TypeScript code as shown below: /** * @typedef Foo * @type {Object} * @property {string} id */ type Foo = { id: string } /** * bar * @returns {Foo} */ function bar(): Foo { const foo:Foo = {id: 'foo'} return f ...

Jasmine and Karma encountered a TypeError stating that the function this.role.toLowerCase is not valid

Currently, I am in the process of writing a test case for a page within an application that our team is actively developing. However, I have encountered a challenging error within one of the test cases that I am struggling to overcome. Below is my Spec fil ...

Determine whether a many-to-many relationship involves a specific entity

I am currently working on developing an API for managing surveys. One challenge I'm facing is determining whether a specific user has moderation privileges for a particular survey. A many-to-many relationship has been set up between the two entities. ...

What is the process of transforming two forms into components and then integrating those components into a view in Angular 5?

Currently, I have two forms running smoothly on the same component as shown in InfoAndQualificationComponent.ts import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl } from "@angular/forms"; @Component({ selector: ...

What is the best way to compare two date strings with the format dd/mm/yyyy using JavaScript?

When attempting to compare a "Date" type of data with an "Any" type of data, the comparison is not functioning as expected. The date is retrieved in the following code: var today = new Date(); var dd = String(today.getDate()).padStart(2, '0'); v ...

Enhance Vuetify functionality using TypeScript for custom components

I'm facing a challenge with extending a Vuetify component and setting default props in TypeScript. While I had success doing this in JavaScript, I am struggling to do the same in TS. Below is an example of how the Component was implemented in JS: imp ...

The type 'elementfinder' cannot be assigned to a parameter of type 'boolean'

Currently, I am working on a function that checks if a checkbox is selected. If it's not checked, then the function should click on it. However, I encountered an error message stating "argument of type 'elementfinder' is not assignable to pa ...

Starting up a pre-existing Angular project on your local machine

I am completely new to Angular and facing difficulties running an existing project on my machine. Despite conducting numerous tests and following various articles, I still cannot get the project to run. Here is the layout of my project files: I have succ ...

Creating a Dynamic Example in Scenario Outline Using Typescript and Cypress-Cucumber-Preprocessor

I have a question that is closely related to the following topic: Behave: Writing a Scenario Outline with dynamic examples. The main difference is that I am not using Python for my Gherkin scenarios. Instead, I manage them with Cypress (utilizing the cypre ...

``If you're looking to integrate a 360-degree product viewer into your Angular application, here is a

I am in need of showcasing a 360 Product viewer consisting of an array of 36 car images in base64 format. Despite attempting to utilize the angular-three-sixty Package by @mediaman, I was only met with an empty canvas. Does anyone have experience in implem ...