I am struggling over what should appear to be a very simple procedure.
In the "geolocate.ts" function "setData", the model indexes? "flightplan" and "config" are shown by Chrome debugger to be "undefined" when referenced from "model.flightplan" or "model.config". The "model" object itself seems to be fine even when expanded in the debugger.
Any thoughts or pointers would be very much appreciated ;)
geolocate.d.ts
export class FmsFlightPlan {
public status: string[];
...
}
export class Config {
public airportIcon: IconSettings;
...
}
export class InitModel {
public config: Config;
public flightplan: FmsFlightPlan;
}
geolocate.ts
import * as passedData from "./geoLocate.d";
let config: passedData.Config;
let flightPlan: passedData.FmsFlightPlan;
export function setModel( json: string): void {
console.log( json); // '{"Config": { "AirportIcon": {...} ...}, {"Flightplan": {"Status": [], ... } ...}' --- As expected (JSONlint OK)
const model: passedData.InitModel = JSON.parse( json);
console.log(model); // Chrome console: {Config: {…}, Flightplan: {…}}
flightPlan = model.flightplan; // flightPlan and config are assigned "undefined"
config = model.config; // "model" looks OK and Intellisense works.
flightplanDraw();
}
TSC generated javascript
function setModel(o) {
console.log(o);
var e = JSON.parse(o);
console.log(e), flightPlan = e.flightplan, config = e.config, flightplanDraw()
}
.NET Core View Javascript
function gmapsReady() {
initMap();
$.getJSON("/Home/GetConfig",
null,
function(data) {
setModel(data);
});
}
.NET MVC Controller
public JsonResult GetConfig()
{
// Load fplan and config objects
...
...
InitModel initModel = new InitModel
{
Flightplan = fplan,
Config = _config
};
string json = JsonConvert.SerializeObject(initModel);
return new JsonResult(json);
}
A first issue seems to be that you are accessing fields like flightplan and config, whereas in the JSON they are FlightPlan and Config. That's why you're getting undefineds.
A slightly bigger issue after that, which will mostly bite you if you plan on adding methods to your classes, is that the thing produced by JSON.parse is a simple JavaScript object, whereas Config, FlightPlan etc are classes, and instances of them would belong to that class. So if you had something like this:
let x = new Config();
x.airportIcon = 'foo';
console.log(x.constructor); // Prints 'Config'
let y = JSON.parse('{"airportIcon": "foo"}');
console.log(y.constructor); // Prints 'Object something or other'
So the two are structurally equivalent, but won't be functionally equivalent. Even by doing a TS cast you won't be able to call a function on y as you would on x. If these are simple DTOs than that's OK. But if not, you need to be explicit about this and do another step of translating from the JS object to your application ones.
Shameless plug: I wrote raynor to automate this exact process - of translating between a DTO type and a more useful JavaScript class.
You can also configure the JSON serializer on .net side to convert field names from PascalCase to 'camelCase`.
Related
Suppose the following scenario:
We have a class that handles a Mongoose connection as below:
export interface IInstance {
name: string;
instance: Mongoose.Mongoose;
}
export default class MongoHandler {
public static instances: IInstance[] = [{
name: 'default',
instance: null,
}];
// Connect to mongo instance and add it to the instances array
public static async connect(name: string, uri: string, options?: object): Promise<void> {
const instance: Mongoose.Mongoose = await Mongoose.connect(uri, options);
const newInstance: IInstance = {
name,
instance,
};
MongoHandler.instances.push(newInstance);
}
// Returns the instance based on the name of instance
public static getInstance(name: string = 'default'): Mongoose.Mongoose {
return this.instances.find(instance => instance.name === name).instance;
}
}
The other module called CarModel is using getInstance() method for creating a model:
export interface ICar {
name: string;
}
const carSchema = new Mongoose.Schema<ICar>(
{
name: {
type: String,
required: true,
},
}
);
const carModel = MongoHandler.getInstance('default').model<ICar>('Car', carSchema, 'Cars');
export default carModel;
We are using carModel in a module called CarController.
In index.ts we are calling these two modules as below:
import
const app = new App(
[
MongoHandler.connect('default', process.env.MONGO_URI),
],
[
new CarController(),
]
);
App is a class for handling express bootstrapping (can be ignored).
While running this code MongoHandler.getInstance('default') is undefined because of the order of dependency resolution (I think)! And resolving MongoHandler.getInstance('default') is followed by MongoHandler.connect() which should be reversed.
How can I solve this?
Best regards
I think there are 2 issues at play here, neither of them having to do with module resolution (which will work just fine as you don't have any circular dependencies).
array.prototype.find returns the FIRST match found, you instantiate instances array with an object that matches the name default, but has no instance. When you connect, you add another object with the name default, but this will be second in the list, thus find will return the original object, which has its instance object set as null.
I would advice removing this default empty instance object for error and code clarity.
your connect function makes use of async-await. But you never await your connect function, thus not guaranteeing that your new connection instance has been made before you are calling getInstance inside of your carController. You should catch the Promise returned by the connect function and await it. If you do not want to delay your CarController instantiation, you can use save this Promise in the MongoHandler and return it with some init function that you call inside of the CarController to make sure the connect has been resolved.
I'm using this library in a TypeScript project.
And this is how my class looks like:
import OnvifManager from 'onvif-nvt'
Class OnvifApi {
// device: any = undefined
device = {} as OnvifDevice
constructor (...params) {
// definition
}
connect (): Promise<any> {
OnvifManager.connect(...params).then((response: OnvifDevice) => {
this.device = response
resolve(response)
}
}
coreService(): Promise<Type[]> {
this.device.add('core')
}
}
And this this interface I created for the response from onvif-nvt
interface OnvifDevice {
address: string
// type
// type
// type
add(name: string): void
}
This class is always giving an error in coreService saying that
this.device.add is not a function.
EDIT: This is the return of the connect method, which returns the whole Camera object.
Things are working when device is defined to any.
How can I do this interface mapping from JavaScript to TypeScript?
The reason is, in the interface, OnvifDevice, add is a mandatory parameter, but the device object is initialised with an empty object.
The dynamic mapping at this.device = response is not happening.
Also please check if the response has add method or not while you debug.
These are the two possible symptoms I see.
In our codebase we have code like below which is unable to detect if a type violation occurred due to programmer error - why does this happen and what's the best way to address this problem?
// forcing a structure on "shape" of the output
interface IPayloadOutput {
[key: string]: string
}
interface MyPayloadOutput extends IPayloadOutput {
myPayloadKey1: string
myPayloadKey2: string
extraKey: string //adding this here doesn't cause compile error if it's not returned
}
// Input data needs that to be transformed to the output
interface MyPayloadInput {
data1: string
data2: string
}
class MyPayloadOutputGenerator extends PayloadOutputGenerator {
public getPayloadKeyValues(args: MyPayloadInput): IPayloadKeyValues {
return {
myPayloadKey1: {key1: args.data1, key2: args.data2},
myPayloadKey2: { key1: args.data1 + '-senor' },
// Why does code not throw compile error if the below field is missing?
// extraKey: { key1: 'extra' }
}
}
}
function consumer(response: MyPayloadOutput): void {
console.log(response)
}
const x = new MyPayloadOutputGenerator().getPayloadOutput<MyPayloadInput, MyPayloadOutput>({
data1: 'hello',
data2: 'wrold',
})
consumer(x) // should throw compiler error if missing `extraString`
The PayloadGenerator takes in the Input and transforms it into the expected Output. But the key extraString in MyPayloadOutput is missing in the data returned but no compiler error is reported? Why is that?
Here's a fiddle showing a running example
For completeness, here's the PayloadGenerator:
// all payloads need key1 and an optional key2.
interface IPayloadDetails {
key1: string
key2?: string
}
// force structure for how we expect payload key/values to look
interface IPayloadKeyValues {
[key: string]: IPayloadDetails
}
abstract class PayloadOutputGenerator {
// source keys from implementing classes. `args` is not typed to be 'anything'
public abstract getPayloadKeyValues(args): IPayloadKeyValues
// Generic method to be used with any input/output combo
public getPayloadOutput<Input, Output>(args: Input): Output {
const payloadKeyValues = this.getPayloadKeyValues(args)
const payloadOutput = {} as Output
Object.keys(payloadKeyValues).forEach(key => {
payloadOutput[key] = JSON.stringify(payloadKeyValues[key]) //do custom encoding here
})
return payloadOutput
}
}
You typecasted (as Output) and omitted Typings at central positions (: IPayloadKeyValues) and over all worked against typescripts type system. By using index types, you made it impossible for typescript to determine what the code actually does. Instead, use Generics from the beginning:
abstract class PayloadOutputGenerator<I, O extends {}> { // already introduce the generic here ...
public abstract getPayloadKeyValues(args: I): O // then you can narrow down this correctly
public getPayloadOutput(args: I) { // and this gets typed correctly automatically
const payloadKeyValues = this.getPayloadKeyValues(args);
const payloadOutput = {} as { [K in keyof O]: string }; // its a mapped type, so lets type it as such
Object.keys(payloadKeyValues).forEach(key => {
payloadOutput[key] = JSON.stringify(payloadKeyValues[key]) //do custom encoding here
});
return payloadOutput
}
}
I'm trying to build a module that returns all methods of a given Java source code using nodeJs. So far I can get the AST tree built using "java-parser" module but I need to traverse it to filter out methods.
code = ' public class CallingMethodsInSameClass
{
public static void main(String[] args) {
printOne();
printOne();
printTwo();
}
public static void printOne() {
System.out.println("Hello World");
}
public static void printTwo() {
printOne();
printOne();
} }';
var javaParser = require("java-parser");
var traverse = require("babel-traverse").default;
var methodsList = [];
traverse(ast, {
enter(path){
//do extraction
}
}
I understand that babel-traverse is for Js but I wanted a way to traverse so I can filter the methods.
I'm getting this error
throw new Error(messages.get("traverseNeedsParent", parent.type));
^
Error: You must pass a scope and parentPath unless traversing a
Program/File. Instead of that you tried to traverse a undefined node
without passing scope and parentPath.
The AST if logged looks like
{ node: 'CompilationUnit',
types:
[ { node: 'TypeDeclaration',
name: [Object],
superInterfaceTypes: [],
superclassType: null,
bodyDeclarations: [Array],
typeParameters: [],
interface: false,
modifiers: [Array] } ],
package: null,
imports: [] }
where a method will be identified by "MethodDeclaration" within types. Any help is appreciated.
The AST is just another JSON object. Try jsonpath.
npm install jsonpath
To extract all methods, just filter on condition node=="MethodDeclaration":
var jp = require('jsonpath');
var methods = jp.query(ast, '$.types..[?(#.node=="MethodDeclaration")]');
console.log(methods);
See here for more JSON path syntax.
Disclaimer: I know, Parse.com shuts down it's hosted service. Still, we will continue to use the framework for a while, so this question is still important to us.
Recently, I started playing around with TypeScript and figured it might enhance my productivity for parse cloud code a lot. So I did some testing and was successfully able to use typescript to write cloud functions and so on. I even included the typing definition for parse via typings.
However, I still don't get one thing: How can I extend Parse.Object in a type-safe manner?
In normal js I would write:
var Foo = Parse.Object.extend("Foo", {
// instance methods
}, {
// static members
});
In order to get type safety, I would like to write something like this in typescript:
class Foo extends Parse.Object {
// static members
// instance methods
}
Is something like this possible? Am I missing out on something?
Yes, this is possible. There are a few steps necessary for this. First, you need to pass the classname to the parent constructor
class Foo extends Parse.Object {
// static members
// instance methods
constructor() {
// Pass the ClassName to the Parse.Object constructor
super('Foo');
}
}
Furthermore, you need to register your class as an Parse object:
Parse.Object.registerSubclass('Foo', Foo);
After this, you can just use it for your query as:
var query = new Parse.Query(Foo);
query.find({
success: obj: Foo[] => {
// handle success case
},
error: error => {
// handle error
}
});
This also works for the new parse server open source project: https://github.com/ParsePlatform/parse-server
If someone like my stumble across the same question, here are my findings so far:
import Parse from "parse";
interface IBase {
readonly createdAt?: Date;
readonly updatedAt?: Date;
}
interface IMyInterface extends IBase {
name: string;
age?: number;
}
class MyClass extends Parse.Object<IMyInterface> {
constructor(attributes: IMyInterface) {
super("MyClass", attributes);
}
}
export default MyClass;
You can then use it like this:
const newObject = new MyClass({ name: theName, age: theAge });
const result = await newObject.save();
const { attributes } = result;
console.log("Save result", attributes);
You will have full TS support for the attributes then.
Hopefully the work on https://www.npmjs.com/package/#parse/react will proceed quickly.