JohnWeisz/TypedJSON

Could not resolve detected property constructor at runtime for custom classes

Closed this issue · 5 comments

Hello everyone,

I could not get typedJson to run so far. At runtime I get the following error for every property with a custom class type, for instance:

@jsonMember on comment.fk_contactid: could not resolve detected property constructor at runtime. 
Are you sure, that you have both "experimentalDecorators" and "emitDecoratorMetadata" in your tsconfig.json?

"experimentalDecorators" and "emitDecoratorMetadata" are both set to true in tsconfig.json. Standard types such as String are resolved without any issue. I'm using Angular 8.2.14 in production and AOT build. I also set import 'core-js/proposals/reflect-metadata'; in polyfills.js.

I was inspecting the issue at runtime. The Reflect.getMetadata("design:type", ...) function
returns undefined for custom classes. I'm now wondering if it could be due to the webpack imported modules in the compiled .js-files (originating from the imported classes in the .ts-file). For the standard type String the type is clearly written, but every custom class is written as [classname]__WEBPACK_IMPORTED_MODULE_, see the following example:

tslib__WEBPACK_IMPORTED_MODULE_0__[/* __decorate */ "c"]([
        typedjson__WEBPACK_IMPORTED_MODULE_6__["jsonMember"],
        Object(typeorm__WEBPACK_IMPORTED_MODULE_1__[/* PrimaryGeneratedColumn */ "n"])("uuid"),
        tslib__WEBPACK_IMPORTED_MODULE_0__[/* __metadata */ "f"]("design:type", String)
    ], comment.prototype, "commentid", void 0);

    tslib__WEBPACK_IMPORTED_MODULE_0__[/* __decorate */ "c"]([
        typedjson__WEBPACK_IMPORTED_MODULE_6__["jsonMember"],
        Object(typeorm__WEBPACK_IMPORTED_MODULE_1__[/* ManyToOne */ "j"])(ɵ0, ɵ1, { /*nullable:false,*/ cascade: ["insert"], eager: true }),
        Object(typeorm__WEBPACK_IMPORTED_MODULE_1__[/* JoinColumn */ "h"])({ name: 'fk_contactid' }),
        tslib__WEBPACK_IMPORTED_MODULE_0__[/* __metadata */ "f"]("design:type", _contact__WEBPACK_IMPORTED_MODULE_3__[/* contact */ "a"])
    ], comment.prototype, "fk_contactid", void 0);

Could that be the cause of the problem and if yes, do you have any ideas on how to solve it? Or does it have another reason?

Thank you very much in advance!

Hey @mgreg89

I cannot tell without knowing your code, so this is just a hint, but my bet is that you are referencing a class before it's defined. Unlike with compile-time types, types at runtime have to be declared in the correct order.

The section in the main readme at https://github.com/JohnWeisz/TypedJSON#class-declaration-order-matters covers this.

Perhaps the error message should be improved to make this clear.

Hello @JohnWeisz

Thank you very much! That seems to really do the trick 🥇
For now, I could only test it by copying one class into the file of another. Since the project has 68 entities/classes this approach is not very feasible. The question for me is now: How can I influence the order of the classes in the main bundle after compilation?
I tried so far:

  • inserting triple slash directive ///

  • explicitly naming the files in the include section of tsconfig.json

How do you change the concatenation order of files in the final bundle?

After a bit more playing around with the above workaround (copy one class/entity in the file of another), I realized that the important part was that both classes were located in the same file. This made it possible that

 tslib__WEBPACK_IMPORTED_MODULE_0__[/* __decorate */ "c"]([
        typedjson__WEBPACK_IMPORTED_MODULE_6__["jsonMember"],
        Object(typeorm__WEBPACK_IMPORTED_MODULE_1__[/* ManyToOne */ "j"])(ɵ0, ɵ1, { /*nullable:false,*/ cascade: ["insert"], eager: true }),
        Object(typeorm__WEBPACK_IMPORTED_MODULE_1__[/* JoinColumn */ "h"])({ name: 'fk_contactid' }),
        tslib__WEBPACK_IMPORTED_MODULE_0__[/* __metadata */ "f"]("design:type", _contact__WEBPACK_IMPORTED_MODULE_3__[/* contact */ "a"])
    ], comment.prototype, "fk_contactid", void 0);

turned into

 tslib__WEBPACK_IMPORTED_MODULE_0__[/* __decorate */ "c"]([
        typedjson__WEBPACK_IMPORTED_MODULE_6__["jsonMember"],
        Object(typeorm__WEBPACK_IMPORTED_MODULE_1__[/* ManyToOne */ "j"])(ɵ0, ɵ1, { /*nullable:false,*/ cascade: ["insert"], eager: true }),
        Object(typeorm__WEBPACK_IMPORTED_MODULE_1__[/* JoinColumn */ "h"])({ name: 'fk_contactid' }),
        tslib__WEBPACK_IMPORTED_MODULE_0__[/* __metadata */ "f"]("design:type", contact)
    ], comment.prototype, "fk_contactid", void 0);

(_contact__WEBPACK_IMPORTED_MODULE_3__[/* contact */ "a"] turned into contact in second last line)

Is there any trick to combine all files of a certain directory to one file before webpack kicks into the compilation process?

Hey @Neos3452,

thank you for the hint! I think that was exactly the underlying issue.

Maybe it helps someone in the future:
I wanted to use typedJson for resolving JSON to TypeORM entities - I know it can be done with TypeORM itself, but database is not initialized at that point in time. Unfortunately, these entities are prone to circular dependency, since they reference each other with their relations (ManyToOne/OneToMany/...). I started to implement an approach (Issue#4190) for eliminating these circuits. The problem with that is, it destroys types for typeJson finally (relations will have an interface as type, which is transformed to Object in "design:type" metadata).
I ended up implementing my own instantiation algorithm, which uses the relation decorators of TypeORM. They use the form type => contact for supplying type information within the decorator, for instance:

@ManyToOne(type => contact, fk_contactid => fk_contactid.comments)
@JoinColumn({ name: 'fk_contactid' })
fk_contactid: contact;

In that way, types can be retrieved regardless of ciruclar dependencies.

Hope it helps someone ;)