Search code examples
typescriptoption-type

How can I declare options in a type-safe way, then access the values?


I'm working with a library that provides a deeply optional interface.

type Option = number | {
  x?: number;
  y?: number;
  z?: number;
}

interface Options {
  a?: Option;
  b?: Option;
  c?: Option;
  d?: Option;
}

function initializeLibrary(options: Options) {
  // ...
}

I want to declare a sparse set of options, use the values myself, then pass the options into the library.

// 1. Declare a sparse set of options.
const opts = {
  a: 3,
  b: 4,
  c: { x: 3 },
};

// 2. Use the option values myself.
console.log("Sum:", opts.a + opts.c.x);

// 3. Pass the options into the library.
initializeLibrary(opts);

This works, but it isn't type-safe. I can add keys to opts that don't belong to Options, and I won't get an error.

const opts = {
  a: 3,
  f: 3, // `f` doesn't belong to Options.
};

initializeLibrary(opts); // No error!

If I declare opts with type Options, then it becomes type-safe, but I get an error when I access opts.a.

const opts : Options = {
  a: 3,
  b: 4,
  c: { x: 3 },
};

console.log(opts.a + opts.c.x); // Error: Object is possibly 'undefined'

How can I declare my options in a type-safe way while still having access to the values?


My best effort is to re-declare the specific parts of the Options interface that I'm using.

interface MyOptions extends Required<Pick<Options, "a" | "b" | "c">> {
  a: number;
  c: { x: number };
}
const opts: MyOptions = {
  a: 3,
  b: 4,
  c: { x: 3 },
};

This works; it will protect me from adding properties that don't exist in Options. But my actual use case is much more complex so I want to trick the computer into doing this work for me.


Solution

  • You can use the satisfies keyword as of v4.9 :)

    const opts = {
      a: 3,
      b: 4,
      c: { x: 3 },
    } satisfies Options;
    
    console.log(opts.a + opts.c.x); // all good
    initializeLibrary(opts); // all good
    
    const opts = {
      a: 3,
      b: 4,
      c: { x: 3 },
      f: 3, // will result in an error
    } satisfies Options;