Typescript - Get uninitialized properties after compilation - class

I am currently writing a wrapper around socket.io. Comming from a very object-oriented background, I want to implement the concept of Models in my framework/wrapper.
If you happen to know socket.io you might know that you get the data that is associated with an event as a parameter, now I have implemented a custom routing system where the handler of the route gets the data in an express.js like request object.
The idea is to have model classes that look something like this:
class XRequestModel
#v.String({ message: 'The username must be a string!' })
public userName: string;
}
And the route event might look something like this:
#RouteConfig({ route: '/something', model: XRequestModel })
class XEvent extends Route {
public on(req: Request<XRequestModel>, res: Response) {
// Handle Event
}
}
And to complete the example here is how the request object might look like:
class Request<T> {
public data: T;
}
Now generics in typescript are very limited since the type information is removed after compilation, I can not use the generic Request parameter ( which is the type of the model ) to get metadata from the model - Metadata, in this case, is the validation decorator. To overcome this issue I give a reference of the Model class to the RouteConfig of the RouteEvent, which is internally used and would allow me to create instances of the model, get the properties and so on...
The idea here is to give the handler of a route, a request object with pre-validated, typesafe data.
The thing holding me back from this, is the fact that unused properties, get removed after compilation by typescript, So I cannot get the metadata of the model. Initializing the class-property would solve this:
class XRequestModel
#v.String({ message: 'The username must be a string!' })
public userName: string = '';
}
But I think this makes for some very verbose syntax, and I dont want to force the user of this wrapper to init all the model properties.
An implementation side-note:
The user of the framework has to register the classes to a 'main' class and from there I can get the Route-class via decorator reflection.
When I try to get the properties of the model without initialized properties - First model example.
// Here the route.config.model refers to the model from the RouteConfig
Object.getOwnPropertyNames(new route.config.model());
>>> []
Here is what I get with initialized properties:
Object.getOwnPropertyNames(new route.config.model());
>>> [ 'userName' ]
Here a link to the GitHub repository: https://github.com/FetzenRndy/SRocket
Note that models are not implemented in this repo yet.
Basically, my question is: How can I get the properties of a class that has uninitialized properties after compilation.

The problem is that if no initialization happens, no code is emitted for the fields, so at runtime the field does not exist on the object until a value is assigned to it.
The simplest solution would be to initialize all fields even if you do so with just null :
class XRequestModel {
public userName: string = null;
public name: string = null;
}
var keys = Object.getOwnPropertyNames(new XRequestModel())
console.log(keys); // [ 'userName', 'name' ]
If this is not a workable solution for you, you can create a decorator that adds to a static field on the class and the walk up the prototype chain to get all fields:
function Prop(): PropertyDecorator {
return (target: Object, propertyKey: string): void => {
let props: string[]
if (target.hasOwnProperty("__props__")) {
props = (target as any)["__props__"];
} else {
props = (target as any)["__props__"] = [];
}
props.push(propertyKey);
};
}
class XRequestModelBase {
#Prop()
public baseName: string;
}
class XRequestModel extends XRequestModelBase {
#Prop()
public userName: string;
#Prop()
public name: string;
}
function getAllProps(cls: new (...args: any[]) => any) : string[] {
let result: string[] = [];
let prototype = cls.prototype;
while(prototype != null) {
let props: string[] = prototype["__props__"];
if(props){
result.push(...props);
}
prototype = Object.getPrototypeOf(prototype);
}
return result;
}
var keys = getAllProps(XRequestModel);
console.log(keys);

Related

How to use type check Loopback / Fireloop PersistedModel

I'm using Fireloop with Loopback 3 and wanting to know how best to create typesafe hooks and remote methods using type checked PersistedModel and Validatable methods. I'd like to change the type of the constructor from ...
constructor(public model: any) { }
to ...
constructor(public model: SomeType) { }
I'd like to make PersistedModel calls like
this.model.count().then((n) => ...);
OR Validatable calls like:
model.validatesLengthOf('code', {
min: 6, max: 12, message: { min: 'too short', max: 'too long'}
});
The Fireloop examples like the one below only use any as type of this.model.
The firestarter model samples and Fireloop documentation were also of no use here.
I know that there is a type called ModelConstructor declared in the fireloop source tree under core/index.d.ts. This interface looks correct because it implements all the PersistedModel and Validatable methods but where is it published in npmjs? Is it already part of the Fireloop server SDK or do I need to npm install it? No idea.
import { Model } from '#mean-expert/model';
/**
* #module Account
* #description
* Write a useful Account Model description.
* Register hooks and remote methods within the
* Model Decorator
**/
#Model({
hooks: {
beforeSave: { name: 'before save', type: 'operation' }
},
remotes: {
myRemote: {
returns: { arg: 'result', type: 'array' },
http: { path: '/my-remote', verb: 'get' }
}
}
})
class Account {
// LoopBack model instance is injected in constructor
constructor(public model: any) { }
// Example Operation Hook
beforeSave(ctx: any, next: Function): void {
console.log('Account: Before Save', ctx.instance);
next();
}
// Example Remote Method
myRemote(next: Function): void {
this.model.find(next);
}
}
module.exports = Account;
Finially, I've also attempted to use the Loopback 3 Typescript definitions but hit more problems as the PersistedModel methods here are all declared as static so fail type checks and return Promise<T> | void. The later means you’re forced to type cast the result back to just Promise<T> so it seems like the type def authors have never actually used them. Is this a bug or am I missing something? Can't find any working examples to prove otherwise.
This is the server side API pain. Client side REST API for Fireloop is also undocumented (lots of example for Real-time API) but none for the REST api it's also supposed to include (just mentioned once in one issue). Would be nice to find it can all be type checked by Typescript.
I found that ModelConstructor validation methods were missing arguments like { message: 'my error message' } and methods like exists() returned Promise<any> instead of Promise<boolean>.
The type definitions in Loopback 3 Type definitions were more complete but were unusable unless fixed as described above.
In the end I used ..
# Used modified type defs from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/loopback/index.d.ts
import { Validatable } from '../types/validatable';
# Fixed static methods and return types.
import { PersistedModel } from '../types/loopback';
constructor(public MyModel: Validatable | PersistedModel) {
let Model = MyModel as Validatable;
Model.validatesLengthOf('code', {
min: 6,
max: 80,
message: { min: 'too short', max: 'too long' } });
Model.validatesFormatOf('email',
{ with: this.reEmail,
message: 'invalid email address',
allowNull: false });
Model.validatesInclusionOf('role', {
in: RoleNames,
message: 'is not valid'
});
}
And in later methods ..
let Model = this.MyModel as PersistedModel;
// knows that isFound is boolean
Model.exists(code).then(isFound => { ... });

howto: optional (nested) class property?

I have a class which I want to have some optional nested properties.
class Input {
stuff {
first_name?: string; // optional
};
however it seems that isn't legal typescript. ; expected
Next is to pull stuff out into an interface
interface IFrom {
id: any;
first_name?: string;
};
class Input {
from:IFrom;
however when i put these in the same file I get
tsPublic property 'from' of exported class has or is using private name 'IFrom'.
I can't make public interface
'public' modifier cannot appear on a module element.
What I've ended up doing is putting the interface in yet another file, but this is getting to be astronaut engineering where every struct and property needs to be in its own file...
Am I missing something about how best to do this?
I want the first_name property to be public but part of a struct.
Want it to be optional.
Prefer fewer individual files.
Thanks!
however it seems that isn't legal typescript. ; expected
The syntax for inline types is incorrect. You are missing a :. The following works fine:
class Input {
stuff: {
first_name?: string; // optional
};
}
That answers the question. But since you tried more stuff:
Public property 'from' of exported class has or is using private name 'IFrom'.
You probably have export class ... that means that you need to do export interface too to export any interface that the class uses.
You should use the keyword export with interfaces and classes, not public.
Here is an example:
module ModuleA {
export interface IFrom {
id: any;
first_name?: string;
}
}
module ModuleB {
export class Input {
from:ModuleA.IFrom;
}
}
var input = new ModuleB.Input();
input.from = {id: 123, first_name: 'Bob'};
alert(input.from.first_name); // Bob

Jackson and REST Android Annotations: Deserialize JSON Array of Objects

I have a REST service which returns data that looks like this:
[
{ bookmark object json here },
{ bookmark object json here },
{ bookmark object json here },
...
]
My REST client class looks like this:
#Rest(rootUrl = Constants.ApiConfig.API_ROOT, converters = {MappingJackson2HttpMessageConverter.class})
public interface RestApiClient {
#Get("/bookmark/read?id={identifier}")
public BookmarkList getBookmarks(String identifier);
}
BookmarkList looks like this:
public class BookmarkList {
List<Bookmark> bookmarks;
#JsonValue
public List<Bookmark> getBookmarks() {
return bookmarks;
}
#JsonCreator
public void BookmarkList(#NotNull List<Bookmark> bookmarks) {
this.bookmarks = bookmarks;
}
}
However, when I utilize this setup, I get the following error:
Could not read JSON: Can not deserialize instance of com.whatever.entity.BookmarkList out of START_ARRAY token
What I want is something like the EventList example at https://github.com/excilys/androidannotations/wiki/Rest-API#get, but that doesn't seem to work for me out of the box.
Is there a way to get this working?
Ho... We have to update this part of documentation. The wrapper solution works but doesn't fit APIs.
If you're looking at the generated code for #Get("url") MyReturnedType testService(), you should see something like this :
return restTemplate.exchange(rootUrl.concat("url"), //
HttpMethod.GET, //
null, //
MyReturnedType.class, //
urlVariables)//
.getBody();
The returned class is injected as a parameter of exchange call. In case of generics collection (like List<MyReturnedType>), we can't inject List.class because of type checking in the return of exchange method.
However, you should be able to use this little trick in your #Rest annotated method :
public class BookmarkList extends List<Bookmark> {
}
I think I misunderstood the example at https://github.com/excilys/androidannotations/wiki/Rest-API#get. I think the array still must be wrapped inside a JSON object in that example (It'd be nice if they included example JSON data).
The data the service I'm connecting to does not return an object wrapping the array like that, so, I altered the REST client to look like this:
#Rest(rootUrl = Constants.ApiConfig.API_ROOT, converters = {MappingJackson2HttpMessageConverter.class})
public interface RestApiClient {
#Get("/bookmark/read?id={identifier}")
public ArrayNode getBookmarks(String identifier);
}
And I wrote a method in another class to iterate the ArrayNode and build the bookmarks:
public List<Bookmark> getBookmarks(Content content) {
ArrayList<Bookmark> bookmarks = new ArrayList<Bookmark>();
ArrayNode bookmarksData = apiClient.getBookmarks(content.getAcid());
for(JsonNode bookmarkData : bookmarksData) {
Bookmark bookmark = objectMapper.convertValue(bookmarkData, Bookmark.class);
bookmarks.add(bookmark);
}
return bookmarks;
}
So it's not as convenient (I had to write more code myself), but I got it working.

How to support embedded maps (with custom value types) in MongoDB GORM?

I would like to have an embedded document referred to by a map (as in 'class A' below). The environment is Grails + GORM + MongoDB.
is that possible, and if yes, how?
class A { // fails with IllegalArgumentException occurred when processing request: can't serialize class X in line 234 of org.bson.BasicBSONEncoder
static mapWith = "mongo"
Map<String, X> map = new HashMap<String, X>()
}
class B { // works
static mapWith = "mongo"
List<X> list = new ArrayList<X>()
}
class C { // works with primitive type values
static mapWith = "mongo"
Map<String, String> map = new HashMap<String, String>()
}
class X {
String data
public X(String data) {
this.data = data
}
}
The embedding works perfectly,as Art Hanzel advised.
However your problem comes from the fact that you try and use List genericity as a sort of constraint :
Map<String, X>
The problem is that Grails couldn't cope well with this syntax, first because Groovy doesn't support genericity.
However, the MongoDB plugin offers a very powerful functionality that lets you define custom type as Domain Class Properties : see here.
In your case you could have
class A {
static mapWith = "mongo"
MyClass map = new MyClass()
}
Then in your src/java for example you could for example implement a
class MyClass extends HashMap<String,X> { }
Then, of course, you have to define a special AbstractMappingAwareCustomTypeMarshaller to specify how to read and write the property in the DB.
An additional step could also be to add a custom validator to class A to check the validity of data...
The MongoDB Grails plugin documentation describes how to make embedded documents:
class Foo {
Address address
List otherAddresses
static embedded = ['address', 'otherAddresses']
}
Off the top of my head, you should be able to access these via the object graph. I don't see any reason why you shouldn't.
myFoo.address.myAddressProperty...

Grails: JSONP callback without id and class in JSON file

I am working on a REST based interface where people get a json file. The client needs to access the file from another Domain. I use jsonp which works so far. My problem is the rendering in Grails. At the moment I use the 'as JSON' to marshalling the object:
render "${params.jsoncallback}(${user as JSON})"
The Json file getting to the client inclused all attributes, incluing the id and class, which I do not want to have in there. In case it is not jsonp, I do it this way, which works great:
render(contentType:'text/json'){
userName user.userName
userImage user.userImage
:
:
}
So how do I get the id and class attributes out of the json when rendering "user as JSON"? Any idea?
best regards,
Klaas
You can get rid of the class and id properties in the JSON result by creating a custom ObjectMarshaller.
// CustomDomainMarshaller.groovy in src/groovy:
import grails.converters.JSON;
import org.codehaus.groovy.grails.web.converters.ConverterUtil;
import org.codehaus.groovy.grails.web.converters.exceptions.ConverterException;
import org.codehaus.groovy.grails.web.converters.marshaller.ObjectMarshaller;
import org.codehaus.groovy.grails.web.json.JSONWriter;
import org.springframework.beans.BeanUtils;
public class CustomDomainMarshaller implements ObjectMarshaller<JSON> {
static EXCLUDED = ['metaClass','class','id','version']
public boolean supports(Object object) {
return ConverterUtil.isDomainClass(object.getClass());
}
public void marshalObject(Object o, JSON json) throws ConverterException {
JSONWriter writer = json.getWriter();
try {
writer.object();
def properties = BeanUtils.getPropertyDescriptors(o.getClass());
for (property in properties) {
String name = property.getName();
if(!EXCLUDED.contains(name)) {
def readMethod = property.getReadMethod();
if (readMethod != null) {
def value = readMethod.invoke(o, (Object[]) null);
writer.key(name);
json.convertAnother(value);
}
}
}
writer.endObject();
} catch (Exception e) {
throw new ConverterException("Exception in CustomDomainMarshaller", e);
}
}
}
You'll need to register in you grails-app/conf/BootStrap.groovy:
class BootStrap {
def init = { servletContext ->
grails.converters.JSON.registerObjectMarshaller(new CustomDomainMarshaller())
}
def destroy = {}
}
This should work in Grails >= 1.1
thanks for the 'quick' reply!
Man, it looks so easy in the end and took so long to figure out.
I got it working doing a map out of the values I needed and rendered them 'as json' like this:
def userProfile = user.get(randomUser)
def jsonData = [
username: userProfile.userName,
userimage: userProfile.userImage,
userstreet: userProfile.userStreet,
:
:
] as JSON
println jsonData
voila, there was the json I needed :)
It doesn't seem to me as if the JSON auto marshaller supports this.
You could use FlexJSON which allows you to exlude certain properties and wrap it into a custom Codec.
Also see here.