I have the following trait
and classes
in Scala
:
sealed trait Algorithm {
val name: String
val formula: String
val parameters: Seq[AlgorithmParameter[Any]]
def enhanceAlgorithm[T](algorithm: T): Unit
}
case class LinearRegressionAlgorithm(override val name: String, override val formula: String) extends Algorithm {}
case class GeneralizedLinearRegressionAlgorithm(override val name: String, override val formula: String) extends Algorithm {}
sealed abstract class AlgorithmParameter[+T](val name: String, val value: T, val selectOptions: Seq[T]) extends EnumEntry
object AlgorithmParameter extends Enum[AlgorithmParameter[AnyVal]] with CirceEnum[AlgorithmParameter[AnyVal]] {
case object MaxIter extends AlgorithmParameter[Int]("maxIter", 100, Seq())
}
I created in TypeScript
the corresponding classes which look like this:
export abstract class Algorithm {
readonly name: string;
readonly formula: string;
readonly parameters: AlgorithmParameter<any>[];
protected constructor(name: string, formula: string, parameters: AlgorithmParameter<any>[]) {
this.name = name;
this.formula = formula;
this.parameters = parameters;
}
}
export class LinearRegressionAlgorithm extends Algorithm {}
export class GeneralizedLinearRegressionAlgorithm extends Algorithm {}
export class AlgorithmParameter<T> {
readonly name: string;
readonly value: T;
readonly selectOptions: T[];
constructor(name: string, value: T, selectOptions: T[]) {
this.name = name;
this.value = value;
this.selectOptions = selectOptions;
}
}
What I'm doing is making a REST request to the backend (Scala part) which returns a sequence of the type Seq[Algorithm]
and I expect that the response will be correctly translated into the Typescript classes, but it doesn't
The REST method looks as follows:
recommend<T extends Algorithm>(body: RecommenderRequest): Observable<T[]> {
return this.http.post<T[]>(environment.baseUrl + this.recommenderPath + this.algoRecommendationsPath, JSON.stringify(body))
.catch((error: any) => Observable.throw(error.json().error || 'Server error'))
}
The response is translated into an array looking as follows:
It seems that the TS algorithm-objects
have been created, but they are encapsulated inside another object and I can't access in an easy way
In order to get the name
of the first algorithm, the call looks as follows:
response[0].LinearRegressionAlgorithm.name
, but I would like to have the Http-response array created in such a way that it would be possible to simply write response[0].name
Angular is not creating your objects of those types. It is simply being told that is what the typing is. Your Scala program is returning the data like is being shown. You will need to do a map operation on each of the results to get the correct objects.
An example is below:
recommend<T extends Algorithm>(body: RecommenderRequest): Observable<T[]> {
return this.http.post<T[]>(environment.baseUrl + this.recommenderPath + this.algoRecommendationsPath, JSON.stringify(body)).pipe(
map(algorithms => algorithms.map(algorithm => this.toAlgorithm(algorithm)),
catchError(error => Observable.throw(error.json().error || 'Server error'))
);
}
private toAlgorithm(algorithm: { [type: string]: Partial<Algorithm> }): Algorithm {
const type = Object.keys(algorithm)[0];
const name = algorithm[type].name;
const formula = algorithm[type].formula;
const parameters = (algorithm[type].parameters || [])
.map(parameter => this.toParameter(parameter));
switch (type) {
case 'LinearRegressionAlgorithm':
return new LinearRegressionAlgorithm(name, formula, parameters);
case 'GeneralizedLinearRegressionAlgorithm':
return new GeneralizedLinearRegressionAlgorithm(name, formula, parameters);
default:
throw new Error('Unsupported algorithm');
}
}
private toParameter(paramerter: Partial<AlgorithmParameter<any>>): AlgorithmParameter<any> {
return new AlgorithmParameter<any>(parameter.name, parameter.value, parameter.selectedOptions);
}
If you don't need actual instances of those algorithms and you just needed the values in the algorithms, you can change your Algorithm
classes to interfaces and it is much simpler:
recommend<T extends Algorithm>(body: RecommenderRequest): Observable<T[]> {
return this.http.post<T[]>(environment.baseUrl + this.recommenderPath + this.algoRecommendationsPath, JSON.stringify(body)).pipe(
map(algorithms => algorithms.map(algorithm => Object.values(algorithm)[0]),
catchError(error => Observable.throw(error.json().error || 'Server error'))
);
}
Doing things this way you would lose any of the info about the type of algorithm, so you would want to add another property onto your algorithm interface for the type and set it in the mapping process above. Just depends on your needs.