Tuesday, 31 December 2019

Implementing OfType in Typescript

I am working on my Linq library for Typescript and wanted to implement OfType. Turns out, this is hard because the generic type arguments in Typescript usually requires a value, i.e. an object instance of type T to get any shape information at run-time. So I ended up passing in a vanilla object setting default property values instead. Here is how my implementation ended up:

function isOfSimilarShape<T>(input: any, compareObject: T): boolean {
  if (input === undefined || input === null || compareObject === undefined || compareObject === null)
    return false;

  let propsOfInput = Object.keys(input);
  let propsOfCompareObject = Object.keys(compareObject);
  //debugger
  let sameShapeOfInputAndCompareObject = propsOfInput.EqualTo(propsOfCompareObject);
  return sameShapeOfInputAndCompareObject;
}

if (!Array.prototype.OfType) {
  Array.prototype.OfType = function <T>(compareObject: T): T[] {
    let result: T[] = [];
    this.forEach(el => {
      //debugger
      let t: T = null;
      if (isOfSimilarShape(el, compareObject))
        result.push(el);
    });
    return result;
  }
}

The following Jasmine test shows its usage:

describe('Array Extensions tests', () => {

  it('can find desired items using OfType of type T', () => {
    let someMixedArray: any[] = [];
    someMixedArray.push(<SomeClass>{ Name: "Foo", Num: 1 });
    someMixedArray.push(<SomeOtherClass>{ SomeName: "BarBazBaze", SomeOtherNum: 813 });
    someMixedArray.push(<SomeClass>{ Name: "FooBaz", Num: 4 });
    someMixedArray.push(<SomeOtherClass>{ SomeName: "BarBaze", SomeOtherNum: 13 });
    someMixedArray.push(<SomeClass>{ Name: "AllyoBaze", Num: 7 });

    let compareObject = <SomeClass>{ Name: "", Num: 0 };
    let filteredArrayBySpecifiedType = someMixedArray.OfType(compareObject);
    console.log(filteredArrayBySpecifiedType);

    expect(filteredArrayBySpecifiedType.All(item => <SomeClass>item !== undefined)).toBe(true, "Expected only items of type SomeOtherClass in the filtered array after running OfType of SomeOtherClass on it.");
  });

It would be nice if we did not have to pass in a vanilla object and populate its properties, but I could not find any tips online or in the Typescript documentation for how to implement extracting type information from generic arguments of Typescript. This is very easy in C#, but while Typescript gives compilation type information, getting runtime information from generic arguments in the Javascript code that Typescript compiles into turned much harder.

3 comments:

  1. The EqualTo method looks like this:
    if (!Array.prototype.EqualTo) {
    Array.prototype.EqualTo = function (compareArray: T): boolean {
    if (!Array.isArray(this) || !Array.isArray(compareArray) || this.length !== compareArray.length)
    return false;
    var arr1 = this.concat().sort();
    var arr2 = compareArray.concat().sort();
    for (var i = 0; i < arr1.length; i++) {
    if (arr1[i] !== arr2[i])
    return false;
    }
    return true;
    }
    }

    ReplyDelete
  2. Did you know there's a 12 word phrase you can tell your partner... that will trigger deep emotions of love and instinctual attractiveness to you buried within his heart?

    That's because deep inside these 12 words is a "secret signal" that fuels a man's impulse to love, treasure and protect you with his entire heart...

    ====> 12 Words That Fuel A Man's Love Instinct

    This impulse is so built-in to a man's mind that it will make him work better than ever before to build your relationship stronger.

    In fact, triggering this dominant impulse is absolutely mandatory to achieving the best possible relationship with your man that the moment you send your man one of the "Secret Signals"...

    ...You'll soon find him expose his heart and mind to you in a way he never expressed before and he'll see you as the only woman in the galaxy who has ever truly fascinated him.

    ReplyDelete
  3. I hit the same issue as well. I ended out implementing:
    export function asType(arg: T): T { return arg; }
    With the usage:
    someMixedArray.push(asType({ Name: "Foo", Num: 1 }));
    This actually verifies that the value conforms to the desired type.
    Did you find a good solution?

    ReplyDelete