Home > Software engineering >  How do I strongly type a function returning a property of an object?
How do I strongly type a function returning a property of an object?

Time:11-15

I have a function named getPropertyReference that returns a reference of a property in an object. It's mostly for my sanity to prevent typing this.object.myNestedObject.property = ... over and over.

Here's the complete function:

export function getPropertyReference(object: {[key: PropertyKey]: any}, prop: PropertyKey): any {
    return {
        get value(): any {
            return object[prop];
        },

        set value(to: any) {
            object[prop] = to;
        }
    }.value; // return the getters and setters without having to access ref.value
}

Usage goes something along the lines of

const ref = getPropertyReference(this.object.myNestedObject, "foo");

ref = 4;

console.log(ref); // 4
console.log(this.object.myNestedObject.foo); // 4

// etc

That code works fine. However, it has a return type of any, and it would be much nicer to get a return type of object[prop]. My code looks like this:

export function getPropertyReference<O = {[key: PropertyKey]: any}, K = keyof O>(object: O, prop: K): O[K] {
    // same code here
}

Typescript complains about this here. "Type 'K' cannot be used to index type 'O'." How can I fix this?

CodePudding user response:

@Thomas That's exactly how I interpreted it as well. A higher order function which creates and returns a closure — functional get (0 args) and set (1 arg) — would work, too

OK, this is possible:

function getPropertyReference<T extends object, K extends keyof T>(object: T, prop: K) {
  function ref(): T[K];
  function ref(value: T[K]): void;
  function ref() {
    if (arguments.length === 0) {
      return object[prop];
    }

    object[prop] = arguments[0];
    return; //Not all code paths return a value.(7030)
  }

  return ref;
}


const example = { name: "bob", age: 12 };

let age = getPropertyReference(example, "age"); // number

console.log(age());

age(18);

console.log(age());

CodePudding user response:

You're very close with the last type you tried. Problem is just that you did = in the generic definition, when you should have used extends. For example, K = keyof O means K can be absolutely any type, but will default to keyof O if no type is supplied. Instead, it needs to be K extends keyof O, which will restrict K to the keys of the object.

export function getPropertyReference<
  O extends { [key: string]: any },
  K extends keyof O
>(object: O, prop: K): O[K] {
  return {
    get value(): O[K] {
      return object[prop];
    },

    set value(to: O[K]) {
      object[prop] = to;
    },
  }.value;
}

const example = { name: "bob", age: 12 };

let age = getPropertyReference(example, "age"); // number
let error = getPropertyReference(example, "doesNotExist"); // compile time error

Playground link

  • Related