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

Unable to display toast notification in React/MUI/Typescript project

Currently tackling error notifications targeted at 400,401, and 500 errors within a large-scale project. I am facing an issue where I want to integrate my ErrorToastNotification component into my layout.tsx file to avoid duplicating it across multiple page ...

DuplicateModelError: Unable to duplicate model after it has been compiled, React.js, MongoDB, TypeScript

In the early stages of developing an application using Next.js, Mongoose, and Typescript, I encountered a persistent issue. Whenever I attempt to send a request through Postman after clicking save, it fails, displaying the error message: OverwriteModelErr ...

MaterialUI Divider is designed to dynamically adjust based on the screen size. It appears horizontally on small screens and vertically on

I am currently using a MaterialUI divider that is set to be vertical on md and large screens. However, I want it to switch to being horizontal on xs and sm screens: <Divider orientation="vertical" variant="middle" flexItem /> I h ...

When a reaction function is triggered within a context, it will output four logs to the console and

My pokemon API application is encountering some issues. Firstly, when I attempt to fetch a pokemon, it continuously adds an infinite number of the same pokemon with just one request. Secondly, if I try to input something again, the application freezes enti ...

What is the method for specifying a null value in Typescript?

I'm curious if this code snippet is accurate, or if there's a better way to define it. Is there an alternative to using error!? I'm unsure of its meaning and would appreciate clarification. ...

Adjust the size and color of text in Chart.js using Angular 5

Why does the font color in chartjs appear as light gray and not print when you want to do so from the page? I tried changing the font color of chartjs in the options attribute, but it didn't work. How can I change the font color in chartjs angular? ...

Helping React and MUI components become mobile responsive - Seeking guidance to make it happen

My React component uses Material-UI (MUI) and I'm working on making it mobile responsive. Here's how it looks currently: https://i.sstatic.net/kxsSD.png But this is the look I want to achieve: https://i.sstatic.net/kJC2m.png Below is the code ...

Issue with Figma React plugin's PostMessage functionality not behaving as anticipated

I am currently working on developing a plugin for Figma, following the react example provided on their GitHub page: https://github.com/figma/plugin-samples/tree/master/react One of the functionalities I have implemented is a button that triggers a specifi ...

The process of extracting all arrays from a JSON object

Within my JSON object, there is a list of countries each with multiple regions stored in an array. My goal is to extract and combine all the regions into one single list. However, when I attempt to map the data, it does not consolidate all the regions as e ...

Executing TypeScript Mocha test cases using ES6 modules

Setting up mocha tests for the TypeScript App in my Rails application has been a bit of a challenge. Initially, I added a basic test to kick things off, but encountered the following error: /home/bernhard/Programs/ruby/cube_trainer/jstests/utils/optional. ...

Angular generates a dynamic interface to fetch data from Wordpress REST API posts (special characters in property names are causing issues)

I've been developing a front-end Angular application that interacts with the Wordpress REST API to fetch and display post data. My goal is to create an interface to handle the responses and render the posts in the template. However, I encountered an ...

Creating a universal wrapper function to serve as a logging tool?

Currently, I am working on a generic JS function that can wrap any other function. The purpose of this wrapper is to execute the wrapped function, log the input and output events, and then return the output for "transparent" logging. However, as I attempt ...

A Guide to Implementing Schema.virtual in TypeScript

After switching from using schema.virtual in JavaScript to TypeScript, I encountered an error when trying to use it with TypeScript. Below is my code: UserSchema.virtual('fullname').get(function () { return `${this.firstName} ${this.lastName}` ...

What is the best way to guarantee an Array filled with Strings?

Which is the proper way to define a potentially array of strings? Promise<Array<string>> Or Promise<string[]> ...

The console is displaying an undefined error for _co.photo, but the code is functioning properly without any issues

I am facing an issue with an Angular component. When I create my component with a selector, it functions as expected: it executes the httpget and renders a photo with a title. However, I am receiving two errors in the console: ERROR TypeError: "_co.photo ...

Retrieve distinct values for the keys from an object array in JavaScript

Here is the structure of my array: const arr1 = [ { "Param1": "20", "Param2": ""8", "Param3": "11", "Param4": "4", "Param5": "18", ...

Adding a badge to a div in Angular 6: What you need to know!

How can I add a badge to a specific div in Angular 6? I have dynamic div elements in my HTML. I want to increase the counter for a specific div only, rather than increasing it for all divs at once. For example, I have five divs with IDs div1, div2, div3, ...

Arrange an array of objects by making a nested API call in Angular

My task involves sorting an array of objects based on the response from the first API call in ascending order. The initial API call returns a list of arrays which will be used for the subsequent API call. The first API call fetches something like this: [0 ...

Exploring the power of Prosemirror with NextJS through Tiptap v2

Greetings everyone, I am a newcomer to Stack Overflow and I am reaching out for assistance regarding an issue that has arisen. The problem at hand pertains to the development of the Minimum Viable Product (MVP) for my startup which specializes in creating ...

"Encountering an issue where attempting to set a property on an undefined variable, despite it being

I've been working on a timer app/component, but I'm running into an issue. The error message reads: Cannot set property startAt of undefined. I've defined it in my dashboard component, so I'm not sure where the problem lies. Any suggest ...