The general priority of type parameter output in TypeScript

I have a factory decorator class taking an initializer function as an argument. In this initializer function, I would like to return an instance corresponding to the involved class type (or its derivative):

 function JsonObject<T>(initializer: (json: any) => T) { return function (target: { new (...args: any[]): T }) { // ... } } 
 @JsonObject(function (json) { return new Animal(); }) class Animal { name: string; } 

Returning an instance of the exact class (as indicated above) works correctly, but ...

Short version

Returning an instance of a derived class fails. I can return the base instance, but not received. I cannot, for example, return a Cat :

 @JsonObject(function (json) { return new Cat(); // Error. }) class Animal{ name: string; } class Cat extends Animal { color: string; } 

... although Cat is an animal. However, I can return Animal instead of Cat (which is not true since Animal is not necessarily Cat) for Cat:

 @JsonObject(function (json) { return new Animal(); // OK, but it shouldn't be }) class Cat extends Animal { color: string; } 

Long version

JsonObject Factory Decoder

The JsonObject function JsonObject similar to a function with a parameter of general type T , which takes a callback function that returns T as an argument and returns a function that takes a new type that returns T Obviously, the latter (return function) is a decorator class .

The compiler will not let me - for example, return a string from this initializer function (or any other type of mismatch), which should be as it is.

Subtype Problem

However, the aforementioned type signature behaves in exactly the opposite way when subtypes are used: from the initializer function I can return a base type, but not a derived type - the following error occurs when a two-stage inheritance pattern is used in the middle class:

 @JsonObject(function (json) { // Test case: return a base type. return new Animal(); // OK, but it shouldn't be: an 'Animal' is not a 'Cat' }) @JsonObject(function (json) { // Test case: return an exact corresponding type. return new Cat(); // OK, as it should be }) @JsonObject(function (json) { // Test case: return a derived type. return new Kitty(); // <-- Error, but it should be OK, a Kitty *is* a Cat }) class Cat extends Animal { color: string; } class Kitty extends Cat { cutenessFactor: number; } 

Error: The type "Cat" cannot be assigned to the type "Kitty". The 'cutenessFactor' property is not present in the 'Cat' type.

I believe that I determined the origin of the error caused by the compiler when generalizing: a parameter of type T is derived from "T" in initializer: (json: any) => T , which means that the error is caused by a JsonObject function that has a common Kitty type, to which Cat , obviously, is not assigned as such, and therefore the class decorator cannot be used in Cat in this case.

I would like T to be deduced from the "return" target type instead, which would solve my problem. How could I do this?

Of course, when I explicitly specify the generic type parameter, it works flawlessly (but this contains redundant information):

 @JsonObject<Cat>(function (json) { return new Kitty(); // OK, since type 'Kitty' is assignable to type 'Cat' }) class Cat extends Animal { } 
+1
source share
1 answer

Did you mean the decorator chain or did you mean this:

 function JsonObject<T>(initializer: (json: any) => T) { return function (target: { new (...args: any[]): T }) { return null; } } @JsonObject(function (json) { return new Foo(); }) class Foo { foo: string; } @JsonObject(function (json) { // Test case: return an exact corresponding type. return new Bar(); // OK, as it should be }) class Bar extends Foo { bar: string; } @JsonObject(function (json) { // Test case: return a derived type. return new Baz(); // Ok }) class Baz extends Bar { baz: string; } 

If you meant the above ^, it compiles fine

0
source

Source: https://habr.com/ru/post/1242922/


All Articles