Accessing properties through recursive type aliases

I am attempting to transcribe this class originally written in JavaScript.

Here is the code snippet provided.

type SchemaDefinition<Schema> = {[Key in keyof Schema]: Schema[Key][] | {[K in keyof Schema[Key]]: SchemaDefinition<Schema[Key][K]>}};
type Middlewares<Schema> = {[Key in keyof Schema]: (value: Schema[Key]) => Schema[Key] | {[K in keyof Schema[Key]]: Middlewares<Schema[Key][K]>}};

class ObjectGenerator<Schema> {
  private schemaDefinition: SchemaDefinition<Schema>;

  constructor(schemaDefinition: SchemaDefinition<Schema> = {} as SchemaDefinition<Schema>) {
    this.schemaDefinition = schemaDefinition;
  }

  generate(middlewares: Middlewares<Schema> = {} as Middlewares<Schema>): Schema {
    const {schemaDefinition} = this;

    return Object.entries(schemaDefinition).reduce((schema, [property, values]) => {
      if (Array.isArray(values)) {
        return {...schema, [property]: (middlewares[property] || ((x: any): any => x))(values[Math.floor(Math.random() * values.length)])};
      } else if (typeof values === "object") {
        return {...schema, [property]: new ObjectGenerator(values).generate(middlewares[property])};
      }

      return schema;
    }, {} as Schema);
  }
}

The error message I encountered reads as follows:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Middlewares<Schema>'. No index signature with a parameter of type 'string' was found on type 'Middlewares<Schema>'

My expectation is for TypeScript not only to infer the types accurately during compile time (such as for the Schema that transforms into a SchemaDefinition) but also to support recursive calls and still predict the types of nested properties in the final output. This way, clients would receive warnings when trying to access a non-existent nested property, invoking a middleware on a property that does not exist, or using a wrong type in the middleware call.

Answer №1

There was an issue with the callback in the Middlewares property that was missing some surrounding parentheses.

The code now appears much cleaner and is more user-friendly with the implementation of a for-in loop. I decided to refactor this as a function since using a class doesn't provide any additional benefits in this scenario.

I had to resort to somewhat unsightly as type assertions, but in this context, it seems justified given that it is a union type and aids the compiler in understanding the types involved.

type SchemaDefinition<Schema> = {[Key in keyof Schema]: Schema[Key][] | {[K in keyof Schema[Key]]: Schema[Key][K][]}};
type Middlewares<Schema> = {[Key in keyof Schema]?: ((value: Schema[Key]) => Schema[Key]) | Middlewares<Schema[Key>>};

const createObjectGenerator = <Schema>(schemaDefinition = {} as SchemaDefinition<Schema>) => {
  return (middlewares = {} as Middlewares<Schema>) => {
    const schema = {} as Schema;

    for (const property in schemaDefinition) {
      if (!schemaDefinition.hasOwnProperty(property)) {
        continue;
      }

      const values = schemaDefinition[property];

      if (Array.isArray(values)) {
        const value = values[Math.floor(Math.random() * values.length)];

        if (typeof middlewares !== "undefined") {
          const middleware = middlewares[property];

          if (typeof middleware === "function") {
            schema[property] = middleware(value);
            continue;
          }
        }

        schema[property] = value;
        continue;
      }

      if (typeof values === "object") {
        if (typeof middlewares !== "undefined") {
          const nestedMiddlewares = middlewares[property];

          if (typeof nestedMiddlewares === "object") {
            schema[property] = createObjectGenerator(values as Schema[typeof property])(nestedMiddlewares) as Schema[typeof property];
            continue;
          }  
        }

        schema[property] = createObjectGenerator(values as Schema[typeof property])() as Schema[typeof property];
        continue;
      }
    }

    return schema;
  }
}

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

The call stack in node.js has been exceeded due to a self-recurring function

My server couldn't handle 1k requests at the same time when using this code to GET a URL. As a workaround, I implemented a recurring function based on the solution provided here. However, when running the code, I encountered the following error. Can y ...

Validation of passwords containing special characters in Angular2

I am working on setting up password validation requirements to ensure the field contains: Both uppercase letters, lowercase letters, numbers and special characters This is my current progress: passwordValueValidator(control) { if (control.value != u ...

The name 'BrowseAnimationModule' cannot be located

Can someone help me figure out how to install or fix this import issue I'm having with the 'animations' directory in @angular/platform-browser/animations not importing properly? import {CommonModule} from '@angular/common'; import ...

The React TypeScript variable does not get updated promptly

I have a textfield that contains text and a button which will process the text. The Button should only be clickable if the textfield is not empty. Here's the code I used to achieve this functionality: const [text, setText] = useState(""); const [activ ...

Explain the concept of a static async create method having identical parameters as the constructor

I have a lot of existing classes that require refactoring to utilize an async constructor. Here's an example: class ClassA { constructor(a: number, b: string, c: string) { //... } //... } I've included an async create method ...

Tips for ensuring the correct typing of a "handler" search for a "dispatcher" style function

Imagine having a structure like this: type TInfoGeneric<TType extends string, TValue> = { valueType: TType, value: TValue, // Correspond to valueType } To prevent redundancy, a type map is created to list the potential valueType and associate i ...

Support for translation of TypeScript files is provided in Angular 7 with i18n

Recent Versions. "@angular-devkit/build-angular": "0.13.4", "@angular-devkit/build-ng-packagr": "0.13.4", "@angular/animations": "7.2.2", "@angular/cdk": "7.2.2", "@angula ...

Having difficulty properly streaming UI components with Vercel's AI-SDK

Recently, I've been diving into the new Vercel's AI-SDK to expand my skills. My current project involves developing a persona generator that takes specific guidelines as inputs and generates persona attributes according to a given Zod schema. B ...

What is the keyboard shortcut in VSCode for creating a new local variable and assigning the result of an expression to it?

When working in Eclipse, there is a useful shortcut (default CTRL + 2 + L) that can create a new local variable to store the result of a selected expression. For example... this.doSomeCalculation(); If you position the cursor over the line above and use ...

Set up a server using Typescript and Node.js on the render.com platform

When attempting to deploy my TypeScript and Node.js application on render.com, I encountered the following error: `Jun 26 01:51:02 PM ==> Starting service with 'node app.ts' Jun 26 01:51:02 PM internal/process/esm_loader.js:74 Jun 26 01:51: ...

Generating a random number to be input into the angular 2 form group index can be done by following these

One interesting feature of my form is the dynamic input field where users can easily add more fields by simply clicking on a button. These input fields are then linked to the template using ngFor, as shown below: *ngFor="let data of getTasks(myFormdata); ...

Choose particular text within the editorState

I'm in the process of developing a sophisticated text editor utilizing draftjs. Take a look at this basic codesandbox to get a better understanding of the issue at hand. One of the challenges I'm facing is with a helper function called getCurren ...

Is it possible for abstract classes to utilize arrays?

Can you have an array inside an abstract class in TypeScript and add items to it? abstract class Department { private data: string[] = []; addData(item: string) { this.data.push(item); } constructor(public name: string) {} printName(): ...

Issues with implementing Dark mode in TailwindCSS with Nuxt.js

After spending a couple of days on this, I'm still struggling to get the dark mode working with Tailwind CSS in Nuxt.js. It seems like there might be an issue with the CSS setup rather than the TypeScript side, especially since I have a toggle that sw ...

Combine various arrays of objects into one consolidated object

Problem: There are untyped objects returned with over 100 different possible keys. I aim to restructure all error objects, regardless of type, into a singular object. const data = [ { "type":"cat", "errors" ...

Terser is causing ng build --prod to fail

When I run ng build --prod on my Angular 7 application (which includes a C# app on the BE), I encounter the following error: ERROR in scripts.db02b1660e4ae815041b.js from Terser Unexpected token: keyword (var) [scripts.db02b1660e4ae815041b.js:5,8] It see ...

Is it possible to make my Toggle/Click event refresh the entire component every time it is clicked?

I'm trying to implement a toggle function to show/hide a specific DIV and dynamically change the button text based on the current state in React Hooks. However, every time I click on it, the entire page seems to re-render in Next.js. I'm not enti ...

The problem with reflect-metadata - __webpack_require__ arises from the absence of the 'Require' definition

I'm facing an issue while trying to launch my angular app in Visual Studio. It seems to be stuck on the "Loading..." section. Upon checking Chrome's error console, I came across the following error: https://i.sstatic.net/1aSZT.jpg Uncaught R ...

Incorrect type deduction on generic array mapping

I have observed an unexpected interaction in the following scenario. When attempting to iterate using the map function with the generic options, I noticed that the item inside is not of the expected type. const foo = <T extends string[]>(options: T, ...

When utilizing useMachine in TypeScript, an error is thrown regarding incompatible types

While attempting to build my first example for a xstate finite machine by following the example on github.com/carloslfu/use-machine, I encountered a TypeScript error that halted my progress: Argument of type '{ actions: { sideEffect: () => void; } ...