0

I'm trying to type this class which was initially written in JavaScript.

Here is the code snippet.

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

And the error I got is.

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>'

What I expect from this is for TypeScript to not only guess the type of the things I pass it at compile time (like for the Schema that gets turned into a SchemaDefinition) but also to support recursive calls and still be able to guess the type of the nested properties in the return output so that clients can be warned when they try to access either a nested property that does not exist, calling a middleware on a property that does not exist or calling a middleware with the wrong type.

Amin NAIRI
  • 2,292
  • 21
  • 20
  • `Object.entries()` is the issue, as the compiler cannot be sure that it knows *all* the properties of an object; see [this answer](https://stackoverflow.com/a/62055863/2887218) for more info. You can do an assertion like `Object.entries(schemaDefinition) as Array<[keyof Schema, Schema[keyof Schema]]>` if you want to sidestep it (no worse than `{} as SchemaDefinition`, which seems more suspicious to me). If this is your main problem I will probably close the issue as a duplicate of others referencing `Object.entries()`; otherwise please elaborate. Good luck! – jcalz Nov 30 '20 at 17:08
  • Thanks, you. Actually, use a for-in loop helped making things cleaner and clearer. I had forgotten some surrounding parenthesis around the union in the middleware for the callback. With some added ugly type assertions I managed to finally have something useful. Thanks. I'll answer my own question. – Amin NAIRI Dec 01 '20 at 09:15

1 Answers1

0

I had an issue with the callback in the Middlewares property that missed some surounding parens.

Also, the code looks way cleaner and is easier to work with using a for-in loop. I also took the opportunity to make this a function since there is no real added-value in using a class in this case.

I had to use some ugly as type assertions, but from what I understand I don't see why not in this case since it is a union type and it helps the compiler know what things are.

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;
  }
}
Amin NAIRI
  • 2,292
  • 21
  • 20