JohnWeisz / TypedJSON

Typed JSON parsing and serializing for TypeScript that preserves type information.
MIT License
603 stars 64 forks source link

Lazy type definition syntax for `@jsonMemberArray`doesn't resolve for circular dependencies #192

Closed yazanhaven closed 2 years ago

yazanhaven commented 2 years ago

In larger applications where a model references another not-yet-loaded model, the readme says to use the lazy type syntax like so to avoid constructor resolution errors:

  import {jsonObject, jsonMember} from 'typedjson';

  @jsonObject
  class Foo {
-     @jsonMember
+     @jsonMember(() => Bar)
      bar: Bar;

-     @jsonMember(Bar)
+     @jsonMember(() => Bar)
      baz: Bar;
  }

  @jsonObject
  class Bar {
-     @jsonMember
+     @jsonMember(() => Foo)
      foo: Foo;
  }

But this syntax for object arrays doesnt work:

// models/Foo.ts
// assume this class has circular class-dependency issues 
class Foo {
    ...
}

// models/Bar.ts
class Bar {
    /*
        doesnt work because of circular class deps
    */
    @jsonArrayMember(Foo)
    fooList1: Foo[];

    /*
        passes the `isTypelike(elementConstructor)` check in the decorator because `elementConstructor` is `() => Foo`
        fails the `isInstanceOf(element, typeDescriptor.elementType.ctor)` check in `convertAsArray(...)`
            because in `isInstanceOf(element, typeDescriptor.elementType.ctor)`, `typeDescriptor.elementType.ctor` is `() => Foo`
    */
    @jsonArrayMember(() => Foo)
    fooList2: Foo[];

    /*
        fails the `isTypelike(elementConstructor)` check in the decorator because `elementConstructor` is `undefined`
    */
    @jsonArrayMember((() => Foo)())
    fooList3: Foo[];
}

Suggestion:

One possible solution would be to update the isInstanceOf function to be able to handle the lazy-loading syntax:


function isInstanceOf<T>(value: any, constructor: Function): boolean {
    if (typeof value === 'number') {
        return constructor === Number;
    } else if (typeof value === 'string') {
        return constructor === String;
    } else if (typeof value === 'boolean') {
        return constructor === Boolean;
    } else if (isObject(value)) {
        if(constructor.prototype) {
          return value instanceof constructor;
        } else if(constructor().prototype) {
          return value instanceof constructor();
        }
    }

    return false;
} 
yazanhaven commented 2 years ago

Apologies - I didnt realize I was referencing the latest docs but I had an older version installed