LoveDev
LoveDev

Reputation: 277

Can't access deep properties/values of object

So below is my example testing code to implement into the wider solution. I've created an object of "services", each property is the service name with the value being the service object. Each service object holds a property of "commands" with the value being an object of commands (classe's).

These commands are executed via the CLI which always returns a string which is then split. index 0 of the array would be the service name, whilst index 1 would be the name of the command.

// Example Classes

class A {

    init() {

        console.log('I Am A');
    }
}

class B {

    init() {

        console.log('I Am B')
    }
}

// Example Services Structure

export const services = {
    service1: {
        commands: {
            a: A
        }
    },
    service2: {
        commands: {
            b: B
        }
    }
}

// Type Declarations

export type Services = typeof services;
export type ServicesKeys = keyof Services;

// Testing

const input = (prompt('Choose a Service') || '').split(' ');

if (input.length !== 2) Deno.exit();

if (input[0] in services) {

    const sKey = input[0] as ServicesKeys;
    const service = services[sKey];

    const commands = service.commands;

    if (input[1] in commands) {

        // Run into issues here as `keyof typeof commands` is `never`
        new commands[input[1] as keyof typeof commands]();
    }
}

Everything essentially works fine until the new commands[input[1] as keyof typeof commands](); as the type of keyof typeof commands is set to never. Which I understand as commands can't have a AND b so keyof has to be never but how do I work with this?

Upvotes: 0

Views: 80

Answers (1)

jsejcksn
jsejcksn

Reputation: 33749

You just need to define the type for your services object, like in the refactor below. If you want to restrict the keys for any part of the structure, you can simply replace string with your union/enum/etc.

Note: I supplied a substitute for the Deno namespace APIs used in your code so that you can run the playground example directly in your browser.

TS Playground link

const Deno = {
  exit (code: number = 0): never {
    throw new Error(`Exited with code ${code}`);
  }
};

// Example Classes

type Command = {
  init (): void;
};

class A implements Command {
  init () {
    console.log('I Am A');
  }
}

class B implements Command {
  init () {
    console.log('I Am B')
  }
}

// Type Declarations

type Service = {
  commands: {
    [commandName: string]: new () => Command;
  };
};

type Services = { [serviceName: string]: Service };

// Example Services Structure

const services: Services = {
  service1: {
    commands: {
      a: A
    }
  },
  service2: {
    commands: {
      b: B
    }
  }
}

// Testing

const input = (prompt('Choose a Service') || '').split(' ');

if (input.length !== 2) Deno.exit();

const [serviceName, commandName] = input;
let command: Command | undefined;

if (serviceName in services) {
  const {commands} = services[serviceName];
  if (commandName in commands) {
    command = new commands[commandName]();
  }
}

command ? command.init() : console.log('No match');

Upvotes: 1

Related Questions