[JS] Typings of the thing model
vavido opened this issue · 7 comments
When accessing the properties
of a feature in typescript, the compiler complains that
TS2339: Property 'xxx' does not exist on type 'object'.
The quickest workaround here is to add an //@ts-ignore
above the line, but that can get bloated quickly.
Another quick solution would be to change the return type of get properies()
in the Feature
class to any
.
A more "proper" approach could be to set the return type to a dictonary, (I assume the properties object is always a dictionary with string keys and arbitrary entries?), but that only moves the problem down one layer in the hierarchy.
What are your thoughts on this?
As a somewhat related sitenote, the current API of the Thing
class is a bit weird, too. To access features of a thing, thing.features?.features?
is needed because the features
field is of Features
type, which then conains a dictonary features
that has the actual features.
Tbh, I don't fully understand the purpose of the Features
class and the IndexedEntityModel
class it inherits from, but assuming this hierarchy layer can not simply be removed, how about returning this._features.features;
inside the features
getter of the thing?
I like the idea of pushing the "problem" down one layer for properties
. The API knows it's a dict, so it can also show it like this. The layer below is domain logic of users and needs to be mapped to the correct data type for that domain by the user.
You're absolutely right about the Features
object (and all other classes extending IndexedEntityModel
). If we were about to change the features
, I would suggest to also change all other occurrences of IndexedEntityModel
. A simple solution would be to make the Features
class an utility class with just static methods to parse to and from object. The currently known FeaturesType
would be the concrete type stored in thing._features
.
Would be nice though if there was only one interface Features
combining the static methods and the dict. The last time I checked, I didn't find a suiteable and nice solution for adding static methods to an interface. Do you know of any?
Normally I would suggest just creating a class instead of an interface, but I think in the case of index signature types like FeatureType
there is no way to do this.
Nvm, in theory one should just be able to add an index signature to a class. However, in the case of the Features
class, this conflicts with the _elements
property of the superclass IndexedEntityModel
I would advocate for simply creating seperate data classes/interfaces and static mapper functions/classes, as I personally find the current approach with things like class IndexedEntityModel<T extends IndexedEntityModel<T, V>, V> extends EntityModel<IndexedEntityModel<T, V>>
very hard to understand.
I thought some more about it and I theoretically like the idea of having the toObject
/fromObject
methods directly on the corresponding classes, but in this case this leads to an extra class (Feature
) that has essentially no purpose other than mapping objects but now also gets dragged along into the class hierarchy.
@ffendt What do you think about seperate mapper & data classes?
Another idea that came up my mind was to build this like we have in the java client. That would be accessing each Feature
in Features
by a getter (getFeature(featureId)
). But actually I like traversing the path with dot-notation.
So from my point of view it would be a good option to use separate mapper & data classes for the classes that currently implement IndexedEntityModel
.
Hi @ffendt, thanks for your input, I'll try to create a clean mapper/data implementation and create a PR here soon
@ffendt I think I found a way to tidy up the sctructure without a need to seperate Mappers and Entity classes:
Typescript allows static methods on a record type, that means the IndexedEntityModel
can be written as
export abstract class IndexedEntityModel<EntryType> implements Record<string, EntryType> {
[key: string]: EntryType;
static fromPlainObject<T>(...): IndexedEntityModel<T> | undefined {
And the concrete classes like this:
export class Features extends IndexedEntityModel<Feature> {
public static fromObject(o: any): Features | undefined {
if (o === undefined) {
return o;
}
return IndexedEntityModel.fromPlainObject<Feature>(o, Feature.fromObject);
}
}
So this means that calls like features.toObject()
need to be refactored to Features.toObject(features)
.
What do you think about that?