I am interested in utilizing Template literal types to symbolize placeholders

Currently, I am in the process of converting existing javascript files into typescript for my business needs.

Below is an example object structure:

[
  {
    // Sample column names givenName, familyName, and picture are provided as examples.
    "givenName": {
      "text": "Foo",
      "type": "text"
    },
    "familyName": {
      "text": "Bar",
      "type": "text"
    },
    "picture": {
      "text": "abc.png",
      "type": "image",
      "thumbnail": "https://example.com/thumbnail/sample.png"
    },
    // Paths to PDF and thumbnail generated from the above information.
    "pdf62882329b9baf800217efe7c": "https://example.com/pdf/genarated_pdf.pdf",
    "thumbnail62882329b9baf800217efe7c": [
      "https://example.com/thumbnail/head.png",
      "https://example.com/thumbnail/tail.png"
    ]
  },
  {
    // ... (same structure as previous object)
  }, // ...
]

The objective is to type the object part like this:

type Row = {
  [headerKey: string]: {
    text: string;
    type: "text";
  } | {
    text: string;
    type: "image";
    thumbnail: string;
  };
  // Paths to the generated PDF and thumbnails.
  pdf+id: string; // path to PDF
  thumbnail+id: [string, string]; // path to thumbnail image (two elements due to two sides of image)
};

Utilizing Template literal types, the typing appears as follows:

type Row = {
  [headerKey: string]: {
    text: string;
    type: "text";
  } | {
    text: string;
    type: "image";
    thumbnail: string;
  };
  [pdfKey: `pdf${string}`]: string;
  [thumbnailKey: `thumbnail${string}`]: [string, string];
};

However, it is not functioning as expected. Is there a method to accurately type this object?

Answer №1

In my opinion, incorporating this logic into a single type in TypeScript seems impractical. However, it is viable to validate such a structure by utilizing a generic function.

By passing an object to a generic function, we can employ a generic type to confirm the object's type.

function checkRow<T extends ValidateRow<T>>(row: T): T {
  return row
}

The crucial element needed now is the generic type.

type ValidateRow<T> = {
  [K in keyof T]: K extends `pdf${string}`
    ? string
    : K extends `thumbnail${string}` 
      ? readonly [string, string]
      : {
          readonly text: string;
          readonly type: "text";
        } | {
          text: string;
          type: "image";
          thumbnail: string;
        }    
}

This type operates on a straightforward if/else logic to determine the accurate type for each property name.

Let's test it with a valid object:

checkRow({    
  "givenName": {
    "text": "Foo",
    "type": "text"
  },
  "familyName": {
    "text": "Bar",
    "type": "text"
  },
  "picture": {
    "text": "abc.png",
    "type": "image",
    "thumbnail": "https://example.com/thumbnail/sample.png"
  },   
  "pdf62882329b9baf800217efe7c":"https://example.com/pdf/genarated_pdf.pdf",
  "thumbnail62882329b9baf800217efe7c":["https://example.com/thumbnail/head.png", "https://example.com/thumbnail/rail.png"]
})
// No issues encountered!

This workflow successfully passes the evaluation. Let's trigger some errors:

checkRow({    
  "givenName": {
    "text": "Foo",
    "type": "text"
  },
  "familyName": {
    "text": "Bar",
    "type": "text"
  },
  "picture": {
    "text": "abc.png",
    "type": "image",
    "thumbnail": "https://example.com/thumbnail/sample.png"
  },   
  "pdf62882329b9baf800217efe7c":"https://example.com/pdf/genarated_pdf.pdf",
  "thumbnail62882329b9baf800217efe7c":["https://example.com/thumbnail/head.png"]
})
// Error raised: Type '[string]' cannot be assigned to type 'readonly [string, string]'


checkRow({    
  "givenName": {
    "text": "Foo",
    "type": "text"
  },
  "familyName": {
    "text": "Bar",
    "type": "text2"
  },
  "picture": {
    "text": "abc.png",
    "type": "image",
    "thumbnail": "https://example.com/thumbnail/sample.png"
  },   
  "pdf62882329b9baf800217efe7c":"https://example.com/pdf/genarated_pdf.pdf",
  "thumbnail62882329b9baf800217efe7c":["https://example.com/thumbnail/head.png", "https://example.com/thumbnail/rail.png"]
})
// Error shown: Type '"text2"' is not compatible with type ""text" | "image"'. Did you intend to use '"text"'

Hence, objects can undergo validation even with intricate logic as long as a generic function is employed.

Interactive Demo Here

Answer №2

By ensuring that all the keys for your object are declared upfront, you can establish the Row type in the following way:

type Info = {
    text: string;
    type: "text";
};
type RowStructure = {
  thumbnail: [string, string];
  pdf: string;
  givenName: Info;
  familyName: Info;
  picture:  {
    text: string;
    type: "image";
    thumbnail: string;
  };
}

type Row = {
  [y in keyof RowStructure 
    as `${y extends 'pdf' ? 
        `pdf${string}`: y extends 'thumbnail'? 
          `thumbnail${string}`: y}`
  ]: RowStructure[y]
}

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

Is it advisable for a component to handle the states of its sub-components within the ngrx/store framework?

I am currently grappling with the best strategy for managing state in my application. Specifically, whether it makes sense for the parent component to handle the state for two subcomponents. For instance: <div> <subcomponent-one> *ngIf=&qu ...

Is it possible to transform a webpack bundled javascript file into typescript source code?

Is it possible to decompile a webpack-bundled JavaScript file into TypeScript source code? I have a bundle.js file that was bundled using webpack, but the original source code files were accidentally deleted. I am hoping to reverse engineer the bundle.js ...

Tips for effectively overriding a method in typescript

Why is this.fullName appearing empty in the show() method? class Person { protected name: string = ""; constructor(name: string) { this.makeSir(name); } makeSir(name: string) { this.name = "sir" + name; } } class M ...

Automatically select the unique item from the list with Angular Material AutoComplete

Our list of document numbers is completely unique, with no duplicates included. I am attempting to implement a feature in Angular Material that automatically selects the unique entry when it is copied and pasted. https://i.stack.imgur.com/70thi.png Curr ...

Error encountered while attempting to generate migration in TypeORM entity

In my project, I have a simple entity named Picture.ts which contains the following: const { Entity, PrimaryGeneratedColumn, Column } = require("typeorm"); @Entity() export class Picture { @PrimaryGeneratedColumn() ...

Exporting a module from a TypeScript definition file allows for seamless sharing

I am in the process of developing a definition file for the Vogels library, which serves as a wrapper for the AWS SDK and includes a property that exports the entire AWS SDK. declare module "vogels" { import AWS = require('aws-sdk'); export ...

Typescript throwing error TS2307 when attempting to deploy a NodeJS app on Heroku platform

Encountering an error when running the command git push heroku master? The build step flags an error, even though locally, using identical NodeJS and NPM versions, no such issue arises. All automated tests pass successfully without any errors. How can this ...

The function of type 'PromiseConstructor' is not executable. Should 'new' be added? React TypeScript

.then causing issues in TypeScript. interface Props { type: string; user: object; setUserAuth: Promise<any>; } const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); if (type === "signup" ...

Utilizing the Filter Function to Eliminate an Element from an Array

I am a beginner in the world of React and I'm currently working on developing a simple timesheet tool where users can add tasks and save them. My tech stack includes React and Typescript. Currently, my main component has an empty array for tasks and ...

Modifying the name of a key in ng-multiselect-dropdown

this is the example data I am working with id: 5 isAchievementEnabled: false isTargetFormEnabled: true name: "NFSM - Pulse" odiyaName: "Pulse or" when using ng-multiselect-dropdown, it currently displays the "name" key. However, I want ...

Having difficulty building a react.js application using Visual Studio 2019

Currently, I am following a helpful tutorial on creating a react.js application using visual studio. At this stage, the tutorial instructs me to open the command prompt and enter the following command: webpack app.tsx --config webpack-config.js (I have ...

Is there a way to define one type parameter directly and another type parameter implicitly?

I am currently utilizing a UI-library that offers an API for constructing tables with a structure similar to this: type Column<Record> = { keys: string | Array<string>; render: (prop: any, record: Record) => React.ReactNode; } The l ...

Angular 12 web version displays error message: "404 not found" for the requested URL

I recently completed my first website using Angular and uploaded it to the server successfully. When browsing through the pages, everything seems fine. However, I encountered an issue when trying to access specific URLs by copying and pasting them into the ...

Removing the @Input decorator in Angular's codebase

I am currently working on an Angular project for a class, and I'm facing an issue where removing the @Input decorator from my component is causing the entire application to not load properly. import { Component, OnInit, Input } from '@angular/ ...

The npm package has been successfully installed, but VS Code is having trouble locating it

Currently, I am in the process of developing a simple npm package known as type-exception using TypeScript. After successful test runs and publication on NPM, I have been able to install it into another project (project B). However, upon importing it as a ...

Move to the top of the page when the next action is activated

I am working with an Angular 8 application. Within the application, I have implemented navigation buttons for next and previous actions. My goal is to ensure that when a user clicks on the "next" button, the subsequent page starts at the top of the page ...

Ensure there is a gap between each object when they are arranged in a

Is there a way to customize the layout of elements in the ratings view so that there is automatic spacing between them? I considered using text (white spaces) for this purpose, but it seems like an inefficient solution. Are there any other alternatives to ...

Angular - optional parameter in route using ngRouter

I have a question regarding using Angular (4) with the @angular/router. I want to be able to include optional parameters in a path style, but am facing some challenges. Currently, my code looks like this: { path: 'cars', component: CarComponent ...

implement some level of control within the ngFor directive in Angular

For instance, let's say I have an ngfor loop: <ng-container *ngFor="let setting of settings | trackBy: trackById"> <button mat-button [matMenuTriggerFor]="menu">Menu</button> <mat-me ...

I'm unsure how to utilize the generic type in this particular scenario. It's a bit confusing to me

Recently, I delved into TypeScript generics and applied them in specific scenarios. However, I encountered some challenges. While working with two different interfaces, I faced a need for flexibility. For instance, I needed to make server requests. func ...