ken
ken

Reputation: 9013

Typing for pushing a new item into an array

I have a model class which manages an internal property _record which is a record from the database. The model class is a generic base class and each model type extends this base class.

I want to provide two convenience methods for modifying properties on the record: setValue for setting a discrete data structure and pushValue to push a discrete data structure onto an array.

/**
 * Allows setting individual properties on the record
 * 
 * @param key the property on the record you want to set
 * @param value the value of the property
 */
public setValue<K extends keyof T>(key: K, value: T[K]) {
  this._record[key] = value;
}

/**
 * Allows setting an individual property on a record where the property
 * must be an array and result of the operation is the addition 
 * of a new array element
 * 
 * @param key the property name on the model's record
 * @param value an element to add to the existing array
 */
public pushValue<K extends keyof T>(key: K, value: keyof T[K]) {
  if (!this._record[key]) {
    this._record[key] = [ value ] as T[K];
  } else if (Array.isArray(this._record[key])) {
    this._record[key] = this._record[key].concat(value);
  }
}

The setValue works fine but I'm struggling with the appropriate typings for the pushValue. The errors are: error 1

error 2

I'm using Typescript 2.4.0

Upvotes: 0

Views: 251

Answers (1)

artem
artem

Reputation: 51629

Array.isArray(this._record[key]) does not work as type guard, this._record[key] remains typed as T[K] after it, probably because of indexed property access (there is similar issue reported here: https://github.com/Microsoft/TypeScript/issues/11483 , resolution is "declined for performance reasons".)

The suggested workaround for that issue is to pass intermediate variable instead of this._record[key] to the type guard, and it indeed works. For a simple variable, its type gets inferred as const v: T[K] & any[], with any[] part coming from Array.isArray type guard, so this code compiles with --noImplicitAny:

abstract class Model<T> {

    _record: T;

    public setValue<K extends keyof T>(key: K, value: T[K]) {
        this._record[key] = value;
    }


    public pushValue<K extends keyof T, V>(key: K, value: V) {
        const v = this._record[key];
        if (!v) {
            this._record[key] = [value] as any;
        } else if (Array.isArray(v)) {
            v.push(value);
        }
    }

}

Upvotes: 1

Related Questions