DEV Community

Matthias Hryniszak
Matthias Hryniszak

Posted on


Type-safe object builder in TS

Long story short - object builder pattern in TypeScript :)

type Builder<Source extends object, Target> = {
  [Prop in keyof Required<Source>]: (value: Source[Prop]) => Builder<Source, Target>
} & {
  build(): Target
Enter fullscreen mode Exit fullscreen mode

This type defines an object that will have methods of the same name as the Source object plus the build() method.

Here's an implementation thereof using Proxy:

function clone<S, T>(source: S): T {
  return JSON.parse(JSON.stringify(source))

function builder<
  Source extends object,
  source: Source,
  convert: (source: Source) => Target = clone
): Builder<Source, Target> {

  const proxy = new Proxy<Builder<Source, Target>>(
    source as any, {
    get(target, prop: string, receiver) {
      if (prop === 'build') return build
      else return (value: any) => setter(
        prop as keyof Source, value

  function build(): Target {
    return convert(source)

  function setter(
    prop: keyof Source,
    value: any
  ): Builder<Source, Target> {
    source[prop] = value
    return proxy

  return proxy
Enter fullscreen mode Exit fullscreen mode

And here's how you could use it:

const b = builder(
    x: 1,
    y: 'Hello'
  source => ({
    o1: { a: source.y, b: source.x },
    o2: { p1: source.x, p2: source.y },

Enter fullscreen mode Exit fullscreen mode

Each call to the builder methods is type-safe so it should be easy to work with it :)

Happy coding!

Oldest comments (0)

11 Tips That Make You a Better Typescript Programmer


1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields


Read the whole post now!