How to anonymous classes in TypeScript - constructor

I am trying to create a helper function, to add currying, to generate common setups for classes. For example:
class Person {
private name: string;
private sex: string;
constructor (name: string, sex: string) {
this.name = name;
this.sex = sex;
}
}
var newPersonSex = function (sex: string) {
return function (name: string) {
return new Person(name, sex);
}
}
var MalePerson = newPersonSex('male');
var FemalePerson = newPersonSex('female');
So that now when MalePerson and FemalePerson objects are created, it actually just returns a new instance of Person instead.
var m = new MalePerson( 'john' );
In order for the type system allow me to still use 'new', MalePerson needs to be a constructor type. For example: 'new (name:string) => Person'. That also needs to be the return type of the function.
However with those declarations added, I cannot return a function from inside 'newPersonSex'. For example this is invalid:
var newPersonSex = function (sex: string) : new(name:string) => Person {
return function (name: string) {
return new Person(name, sex);
}
}
How can I create and return a new constructor function or class on the fly?

Try this definition of newPersonSex:
var newPersonSex = function (sex: string) {
return <new (name: string) => Person> <any> function (name: string) {
return new Person(name, sex);
}
}
The problem with your code is that functions just have call signatures, not construct signatures. My example above works around this by first asserting that the type of that function is any, and then asserting that the any type is actually a constructor.
Note that the type of newPersonSex will be inferred from its return type so no need to add a function annotation.

Related

Parsing Complex JSON Objects with inheritance

I'm building a batch process that includes a number of steps of varying types.
export interface IStep {
id: number;
icon: string;
name: string;
selected: boolean;
}
export class InitStep implements IStep {
id: number;
icon: string;
name: string;
selected = false;
}
export class InputStep implements IStep {
id: number;
icon: string;
name: string;
selected = false;
primaryKey: string;
file: File;
}
export class QueryStep implements IStep {
constructor () {
this.filters = [];
this.output_fields = [];
this.table_fields = [];
const filter = new Filter;
this.filters.push(filter);
}
get input_ids(): number[] {
return this.filters.map(filter => filter.input_id);
}
id: number;
icon: string;
name: string;
selected = false;
table: string;
table_fields: string[];
filters: Filter[];
output_fields: string[];
}
export class OutputStep implements IStep {
constructor() {
this.fields = [];
}
id: number;
icon: string;
name: string;
selected = false;
fields: string[];
}
export class DeliveryStep implements IStep {
constructor() {
this.output_ids = [];
}
id: number;
icon: string;
name: string;
selected = false;
output_ids: number[];
format: BatchOutputType;
frequency: BatchFrequencyType;
email: string;
password: string;
}
I want to be able to have an array of any combination/number of these steps and be able to save them to and read from localstorage.
const key = 'notgunnawork';
localStorage.setItem(key, JSON.stringify(this.steps));
const s = JSON.parse(key) as IStep[];
I knew there was a snowball's chance in hell this was going to parse correctly, obviously the parser doesn't know which steps belong to what classes ultimately. I was just wondering if there was a simple way to get my array to come out looking the same way it went in. I'll eventually be posting this list to the server and would like my .Net Core code to also be able to parse this JSON without me having to make a custom parser.
EDIT
Added the full classes of what Im trying to serialize, for more detail. The error I'm getting whenever I try to serialize and then deserialize is: "Unexpected token o in JSON at position 1"
So, I'm going to answer what I think your issue is, and if I'm wrong then feel free to ignore me 🙂
Your problem is that you have a bunch of classes with methods but when you serialize instances of these to JSON and then deserialize them back, you end up with plain-old JavaScript objects and not instances of your classes. One way to handle this is to use a custom deserializer which knows about your classes and can "hydrate" or "revive" the plain-old JavaScript objects into genuine class instances. The JSON.parse() function allows you to specify a callback parameter called reviver which can be used to do just that.
First, we need to set up a system by which the reviver will know about your serializable classes. I'm going to use a class decorator which will add each class constructor to a registry the reviver can use. We will require that a serializable class constructor be assignable to a type we can call Serializable: it needs to have a no-argument constructor and the things it constructs need to have a className property:
// a Serializable class has a no-arg constructor and an instance property
// named className
type Serializable = new () => { readonly className: string }
// store a registry of Serializable classes
const registry: Record<string, Serializable> = {};
// a decorator that adds classes to the registry
function serializable<T extends Serializable>(constructor: T) {
registry[(new constructor()).className] = constructor;
return constructor;
}
Now, when you want to deserialize some JSON, you can check if the serialized thing has a className property that's a key in the registry. If so, you use the constructor for that classname in the registry, and copy properties into it via Object.assign():
// a custom JSON parser... if the parsed value has a className property
// and is in the registry, create a new instance of the class and copy
// the properties of the value into the new instance.
const reviver = (k: string, v: any) =>
((typeof v === "object") && ("className" in v) && (v.className in registry)) ?
Object.assign(new registry[v.className](), v) : v;
// use this to deserialize JSON instead of plain JSON.parse
function deserializeJSON(json: string) {
return JSON.parse(json, reviver);
}
Okay now that we have that, let's make some classes. (I'm using your original definitions here, before your edits.) Note that we are required to add a className property and we must have a no-arg constructor (this happens for free if you don't specify a constructor, since the default constructor is no-arg):
// mark each class as serializable, which requires a className and a no-arg constructor
#serializable
class StepType1 implements IStep {
id: number = 0;
name: string = "";
prop1: string = "";
readonly className = "StepType1"
}
#serializable // error, property className is missing
class OopsNoClassName {
}
#serializable // error, no no-arg constructor
class OopsConstructorRequiresArguments {
readonly className = "OopsConstructorRequiresArguments"
constructor(arg: any) {
}
}
#serializable
class StepType2 implements IStep {
id: number = 0;
name: string = "";
prop2: string = "";
prop3: string = "";
prop4: string = "";
readonly className = "StepType2"
}
#serializable
class StepType3 implements IStep {
id: number = 0;
name: string = "";
prop5: string = "";
prop6: string = "";
readonly className = "StepType3"
}
Now let's test it out. Make some objects as you would normally do, and put them in an array:
// create some objects of our classes
const stepType1 = new StepType1();
stepType1.id = 1;
stepType1.name = "Alice";
stepType1.prop1 = "apples";
const stepType2 = new StepType2();
stepType2.id = 2;
stepType2.name = "Bob";
stepType2.prop2 = "bananas";
stepType2.prop3 = "blueberries";
stepType2.prop4 = "boysenberries";
const stepType3 = new StepType3();
stepType3.id = 3;
stepType3.name = "Carol";
stepType3.prop5 = "cherries";
stepType3.prop6 = "cantaloupes";
// make an array of IStep[]
const arr = [stepType1, stepType2, stepType3];
And let's have a function which will examine the elements of an array and check to see if they are instances of your classes:
// verify that an array of IStep[] contains class instances
function verifyArray(arr: IStep[]) {
console.log("Array contents:\n" + arr.map(a => {
const constructorName = (a instanceof StepType1) ? "StepType1" :
(a instanceof StepType2) ? "StepType2" :
(a instanceof StepType3) ? "StepType3" : "???"
return ("id=" + a.id + ", name=" + a.name + ", instanceof " + constructorName)
}).join("\n") + "\n");
}
Let's make sure it works on arr:
// before serialization, everything is fine
verifyArray(arr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
Then we serialize it:
// serialize to JSON
const json = JSON.stringify(arr);
To demonstrate your original problem, let's see what happens if we just use JSON.parse() without a reviver:
// try to deserialize with just JSON.parse
const badParsedArr = JSON.parse(json) as IStep[];
// uh oh, none of the deserialized objects are actually class instances
verifyArray(badParsedArr);
// Array contents:
// id=1, name=Alice, instanceof ???
// id=2, name=Bob, instanceof ???
// id=3, name=Carol, instanceof ???
As you can see, the objects in badParsedArr do have the id and name properties (and the other class-specific instance properties like prop3 if you checked) but they are not instances of your classes.
Now we can see if the problem is fixed by using our custom deserializer:
// do the deserialization with our custom deserializer
const goodParsedArr = deserializeJSON(json) as IStep[];
// now everything is fine again
verifyArray(goodParsedArr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
Yes, it works!
The above method is fine, but there are caveats. The main one: it will work if your serializable classes contain properties which are themselves serializable, as long as your object graph is a tree, where each object appears exactly once. But if you have an object graph with any kind of cycle in it (meaning that the same object appears multiple times if you traverse the graph multiple ways) then you will get unexpected results. For example:
const badArr = [stepType1, stepType1];
console.log(badArr[0] === badArr[1]); // true, same object twice
const badArrParsed = deserializeJSON(JSON.stringify(badArr));
console.log(badArrParsed[0] === baddArrParsed[1]); // false, two different objects
In the above case, the same object appears multiple times. When you serialize and deserialize the array, your new array contains two different objects with the same property values. If you need to make sure that you only deserialize any particular object exactly once, then you need a more complicated deserialize() function which keeps track of some unique property (like id) and returns existing objects instead of creating new ones.
Other caveats: this assumes your serializable classes have instance properties consisting only of other serializable classes as well as JSON-friendly values like strings, numbers, arrays, plain objects, and null. If you use other things, like Dates, you will have to deal with the fact that those serialize into strings.
Exactly how complicated serialization/deserialization is for you depends heavily on your use case.
Okay, hope that helps. Good luck!

Exists any way to generate JSON declaration from a typescript class?

Example
class A {
constructor(public val1: number, public val2: number,public val3: string, public b: B) {
}
}
class B {
constructor(public val4: boolean, public val5: number) {
}
}
exists any function that receives class A and return the JSON structure of the class, not matter if it's just visual, return this:
{val1: number, val2: number, val3: string, b: {val4: boolean, val5: number}}
a class in typescript it's no more that a hash of {nameProperty:data type, ...}
I want the class that only have properties, has not methods.
I'll better explain what I wrote in my comment.
In typescript when you define a class you need to define the members in it, but when it's compiled to js the members aren't really being added to the class.
Consider the following typescript:
class A {
str: string;
num: number;
}
It compiles to:
var A = (function () {
function A() {
}
return A;
}());
As you can see there's no trace of the str and num members in the js code.
When you assign values:
class A {
constructor(public str: string, public num: number) {}
}
It compiles to:
var A = (function () {
function A(str, num) {
this.str = str;
this.num = num;
}
return A;
}());
Here you can see str and num, but notice that they are added to the instance in the constructor, they are not added to the prototype (unlike methods) or to A, so there's no way to access them until you have an instance.
Because of that you can not get a mapping of properties in a class.
You can use the reflect-metadata package to save the members meta data.

Typescript: cannot create property inside function

I'm using TypeScript in my project and i am not able to assign a property to anything. I'm attempting to grab some data from a service and assign it to a private object declared using the constructor. However, i keep getting the same error: TypeError: Cannot create property [some property here] on [some data here].
Here's my code:
module MyModule.Controllers {
"use strict";
export interface ICurrentUser {
DisplayName: string;
Features: string[];
}
export class DashboardController {
authenticationService = new Services.AuthenticationService(this.$http);
static $inject = ["$http"];
constructor(private $http: ng.IHttpService, private currentUser: ICurrentUser, private currentUserFeatures: string[])
{
this.getCurrentUser();
this.getUserGroupForCurrentUser();
return;
}
getUserGroupForCurrentUser = () => {
this.authenticationService.getUserGroupForCurrentUser().then((authenticationId) => {
this.currentUser.Features = authenticationFeatures;
});
}
getCurrentUser = () => {
this.authenticationService.getCurrentUser().then((user) => {
this.currentUser.DisplayName = userName;
});
}
}
}
Cannot create property 'DisplayName' on string 'John Doe' –
This is on the following code:
this.currentUser.DisplayName = userName;
Seems that at runtime this.currentUser is actually a string John Doe. This points to the fact that the type declarations (which are effectively hints to the compiler about external systems not in its control) do not match the actual external system at runtime.

Call constructor on TypeScript class without new

In JavaScript, I can define a constructor function which can be called with or without new:
function MyClass(val) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
}
this.val = val;
}
I can then construct MyClass objects using either of the following statements:
var a = new MyClass(5);
var b = MyClass(5);
I've tried to achieve a similar result using the TypeScript class below:
class MyClass {
val: number;
constructor(val: number) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
}
this.val = val;
}
}
But calling MyClass(5) gives me the error Value of type 'typeof MyClass' is not callable. Did you mean to include 'new'?
Is there any way I can make this pattern work in TypeScript?
What about this? Describe the desired shape of MyClass and its constructor:
interface MyClass {
val: number;
}
interface MyClassConstructor {
new(val: number): MyClass; // newable
(val: number): MyClass; // callable
}
Notice that MyClassConstructor is defined as both callable as a function and newable as a constructor. Then implement it:
const MyClass: MyClassConstructor = function(this: MyClass | void, val: number) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
} else {
this!.val = val;
}
} as MyClassConstructor;
The above works, although there are a few small wrinkles. Wrinkle one: the implementation returns MyClass | undefined, and the compiler doesn't realize that the MyClass return value corresponds to the callable function and the undefined value corresponds to the newable constructor... so it complains. Hence the as MyClassConstructor at the end. Wrinkle two: the this parameter does not currently narrow via control flow analysis, so we have to assert that this is not void when setting its val property, even though at that point we know it can't be void. So we have to use the non-null assertion operator !.
Anyway, you can verify that these work:
var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
Hope that helps; good luck!
UPDATE
Caveat: as mentioned in #Paleo's answer, if your target is ES2015 or later, using class in your source will output class in your compiled JavaScript, and those require new() according to the spec. I've seen errors like TypeError: Class constructors cannot be invoked without 'new'. It is quite possible that some JavaScript engines ignore the spec and will happily accept function-style calls also. If you don't care about these caveats (e.g., your target is explicitly ES5 or you know you're going to run in one of those non-spec-compliant environments), then you definitely can force TypeScript to go along with that:
class _MyClass {
val: number;
constructor(val: number) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
}
this.val = val;
}
}
type MyClass = _MyClass;
const MyClass = _MyClass as typeof _MyClass & ((val: number) => MyClass)
var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
In this case you've renamed MyClass out of the way to _MyClass, and defined MyClass to be both a type (the same as _MyClass) and a value (the same as the _MyClass constructor, but whose type is asserted to also be callable like a function.) This works at compile-time, as seen above. Whether your runtime is happy with it is subject to the caveats above. Personally I'd stick to the function style in my original answer since I know those are both callable and newable in es2015 and later.
Good luck again!
UPDATE 2
If you're just looking for a way of declaring the type of your bindNew() function from this answer, which takes a spec-conforming class and produces something which is both newable and callable like a function, you can do something like this:
function bindNew<C extends { new(): T }, T>(Class: C & {new (): T}): C & (() => T);
function bindNew<C extends { new(a: A): T }, A, T>(Class: C & { new(a: A): T }): C & ((a: A) => T);
function bindNew<C extends { new(a: A, b: B): T }, A, B, T>(Class: C & { new(a: A, b: B): T }): C & ((a: A, b: B) => T);
function bindNew<C extends { new(a: A, b: B, d: D): T }, A, B, D, T>(Class: C & {new (a: A, b: B, d: D): T}): C & ((a: A, b: B, d: D) => T);
function bindNew(Class: any) {
// your implementation goes here
}
This has the effect of correctly typing this:
class _MyClass {
val: number;
constructor(val: number) {
this.val = val;
}
}
type MyClass = _MyClass;
const MyClass = bindNew(_MyClass);
// MyClass's type is inferred as typeof _MyClass & ((a: number)=> _MyClass)
var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
But beware the the overloaded declarations for bindNew() don't work for every possible case. Specifically it works for constructors which take up to three required parameters. Constructors with optional paramaters or multiple overload signatures will probably not be properly inferred. So you might have to tweak the typings depending on use case.
Okay, hope that helps. Good luck a third time.
UPDATE 3, AUG 2018
TypeScript 3.0 introduced tuples in rest and spread positions, allowing us to easily deal with functions of an arbitrary number and type of arguments, without the above overloads and restrictions. Here's the new declaration of bindNew():
declare function bindNew<C extends { new(...args: A): T }, A extends any[], T>(
Class: C & { new(...args: A): T }
): C & ((...args: A) => T);
The keyword new is required for ES6 classes:
However, you can only invoke a class via new, not via a function call (Sect. 9.2.2 in the spec) [source]
Solution with instanceof and extends working
The problem with most of the solution I've seen to
use x = X() instead of x = new X()
are:
x instanceof X doesn't work
class Y extends X { } doesn't work
console.log(x) prints some other type than X
sometimes additionally x = X() works but x = new X() doesn't
sometimes it doesn't work at all when targeting modern platforms (ES6)
My solutions
TL;DR - Basic usage
Using the code below (also on GitHub - see: ts-no-new) you can write:
interface A {
x: number;
a(): number;
}
const A = nn(
class A implements A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
);
or:
class $A {
x: number;
constructor() {
this.x = 10;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A = nn($A);
instead of the usual:
class A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
to be able to use either a = new A() or a = A()
with working instanceof, extends, proper inheritance and support for modern compilation targets (some solutions only work when transpiled to ES5 or older because they rely on class translated to function which have different calling semantics).
Full examples
#1
type cA = () => A;
function nonew<X extends Function>(c: X): AI {
return (new Proxy(c, {
apply: (t, _, a) => new (<any>t)(...a)
}) as any as AI);
}
interface A {
x: number;
a(): number;
}
const A = nonew(
class A implements A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
);
interface AI {
new (): A;
(): A;
}
const B = nonew(
class B extends A {
a() {
return this.x += 2;
}
}
);
#2
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
class $A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A: MC<A> = nn($A);
Object.defineProperty(A, 'name', { value: 'A' });
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
const B: MC<B> = nn($B);
Object.defineProperty(B, 'name', { value: 'B' });
#3
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
type $c = { $c: Function };
class $A {
static $c = A;
x: number;
constructor() {
this.x = 10;
Object.defineProperty(this, 'constructor', { value: (this.constructor as any as $c).$c || this.constructor });
}
a() {
return this.x += 1;
}
}
type A = $A;
var A: MC<A> = nn($A);
$A.$c = A;
Object.defineProperty(A, 'name', { value: 'A' });
class $B extends $A {
static $c = B;
a() {
return this.x += 2;
}
}
type B = $B;
var B: MC<B> = nn($B);
$B.$c = B;
Object.defineProperty(B, 'name', { value: 'B' });
#2 simplified
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
class $A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A: MC<A> = nn($A);
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
const B: MC<B> = nn($B);
#3 simplified
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
class $A {
x: number;
constructor() {
this.x = 10;
}
a() {
return this.x += 1;
}
}
type A = $A;
var A: MC<A> = nn($A);
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
var B: MC<B> = nn($B);
In #1 and #2:
instanceof works
extends works
console.log prints correctly
constructor property of instances point to the real constructor
In #3:
instanceof works
extends works
console.log prints correctly
constructor property of instances point to the exposed wrapper (which may be an advantage or disadvantage depending on the circumstances)
The simplified versions don't provide all meta-data for introspection if you don't need it.
See also
My answer to: In TypeScript, can a class be used without the "new" keyword?
My GitHub repo with more examples: https://github.com/rsp/ts-no-new
My workaround with a type and a function:
class _Point {
public readonly x: number;
public readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export type Point = _Point;
export function Point(x: number, y: number): Point {
return new _Point(x, y);
}
or with an interface:
export interface Point {
readonly x: number;
readonly y: number;
}
class _PointImpl implements Point {
public readonly x: number;
public readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export function Point(x: number, y: number): Point {
return new _PointImpl(x, y);
}
TL;DR
If you are targeting ES6 and you really want to use class to store your data, not a function:
Create a function that simply invokes your class constructor with its arguments;
Set that function's prototype to the prototype of your class.
From now you are able to call that function either with or without new keyword to generate new class instances.
Typescript playground
Typescript provides an ability to create such a function (let's call it a "callable constructor") in a strongly typed way. Well, any type is necessary in intermediate type definitions (replacing it with unknown causes errors), but this fact will not affect your experience.
First of all we need to define basic types to describe entities we are working with:
// Let's assume "class X {}". X itself (it has type "typeof X") can be called with "new" keyword,
// thus "typeof X" extends this type
type Constructor = new(...args: Array<any>) => any;
// Extracts argument types from class constructor
type ConstructorArgs<TConstructor extends Constructor> =
TConstructor extends new(...args: infer TArgs) => any ? TArgs : never;
// Extracts class instance type from class constructor
type ConstructorClass<TConstructor extends Constructor> =
TConstructor extends new(...args: Array<any>) => infer TClass ? TClass : never;
// This is what we want: to be able to create new class instances
// either with or without "new" keyword
type CallableConstructor<TConstructor extends Constructor> =
TConstructor & ((...args: ConstructorArgs<TConstructor>) => ConstructorClass<TConstructor>);
The next step is to write a function that accepts regular class constructors and creates corresponding "callable constructors".
function CreateCallableConstructor<TConstructor extends Constructor>(
type: TConstructor
): CallableConstructor<TConstructor> {
function createInstance(
...args: ConstructorArgs<TConstructor>
): ConstructorClass<TConstructor> {
return new type(...args);
}
createInstance.prototype = type.prototype;
return createInstance as CallableConstructor<TConstructor>;
}
Now all we have to do is to create our "callable constructor" and check it really works.
class TestClass {
constructor(readonly property: number) { }
}
const CallableTestConstructor = CreateCallableConstructor(TestClass);
const viaCall = CallableTestConstructor(56) // inferred type is TestClass
console.log(viaCall instanceof TestClass) // true
console.log(viaCall.property) // 56
const viaNew = new CallableTestConstructor(123) // inferred type is TestClass
console.log(viaNew instanceof TestClass) // true
console.log(viaNew.property) // 123
CallableTestConstructor('wrong_arg'); // error
new CallableTestConstructor('wrong_arg'); // error
I like #N. Kudryavtsev solution for creation smart instance factories (constructor wrapping with CreateCallableConstructor). But simple Reflect.construct(type, args) works perfectly if using any[] args is enough.
Here is the example with mobx (v5), which shows that there is no problems with prototypes and decorators:
import { observable, reaction } from "mobx";
class TestClass {
#observable
stringProp: string;
numProp: number;
constructor(data: Partial) {
if (data) {
Object.assign(this, data);
}
}
}
var obj = Reflect.construct(TestClass, [{numProp: 123, stringProp: "foo"}]) as TestClass;
// var obj = new TestClass({numProp: 123, stringProp: "foo"});
console.log(JSON.stringify(obj));
reaction(() => obj.stringProp, v => {
console.log(v);
}
);
obj.stringProp = "bar";
And even this simple wrapper functions works:
type Constructor = new (...args: any[]) => any;
const createInstance = (c: Constructor, ...args) => new c(...args);
var obj = createInstance(TestClass, {numProp: 123, stringProp: "foo"});
// or
const createInstance1 = (c: Constructor) => (...args) => new c(...args);
var obj1 = createInstance(TestClass)({numProp: 123, stringProp: "foo"}, 'bla');
Here's how I've solved this in jest for testing groups of immutable models. The makeHash function isn't doing anything special, just a utility creating short, random strings from uuid().
The 'magic' for me was declaring type as new (...args: any[]) => any allowing it to be 'newed' as let model = new set.type(...Object.values(set.args));. So, less about getting around new and more about working in 'newable' forms.
// models/oauth.ts
export class OAuthEntity<T = string> {
constructor(public readonly id: T) {}
[key: string]: any;
}
export class OAuthClient extends OAuthEntity {
/**
* An OAuth Client
* #param id A unique string identifying the client.
* #param redirectUris Redirect URIs allowed for the client. Required for the authorization_code grant.
* #param grants Grant types allowed for the client.
* #param accessTokenLifetime Client-specific lifetime of generated access tokens in seconds.
* #param refreshTokenLifetime Client-specific lifetime of generated refresh tokens in seconds
* #param userId The user ID for client credential grants
*/
constructor(
public readonly id: string = '',
public readonly redirectUris: string[] = [],
public readonly grants: string[] = [],
public readonly accessTokenLifetime: number = 0,
public readonly refreshTokenLifetime: number = 0,
public readonly userId?: string,
public readonly privateKey?: string
) {
super(id);
}
}
// models/oauth.test.ts
import { makeHash, makePin } from '#vespucci/utils';
import { OAuthEntity, OAuthClient } from '#vespucci/admin/server/models/oauth';
type ModelData = { type: new (...args: any[]) => any; args: { [key: string]: any }; defs?: { [key: string]: any } };
describe('Model Tests', () => {
const dataSet: ModelData[] = [
{ type: OAuthEntity, args: { id: makeHash() } },
{
type: OAuthClient,
args: {
id: makeHash(),
redirectUris: [makeHash()],
grants: [makeHash()],
accessTokenLifetime: makePin(2),
refreshTokenLifetime: makePin(2),
userId: makeHash(),
privateKey: makeHash(),
},
},
{
type: OAuthClient,
args: {},
defs: {
id: '',
redirectUris: [],
grants: [],
accessTokenLifetime: 0,
refreshTokenLifetime: 0,
},
},
];
dataSet.forEach((set) => {
it(`Creates ${set.type.name} With ${Object.keys(set.args).length} Args As Expected`, () => {
let model!: any;
const checkKeys = Object.keys(set.args).concat(Object.keys(set.defs || {}).filter((k) => !(k in set.args)));
const checkValues: any = checkKeys
.map((key) => ({ [key]: set.args[key] || set.defs?.[key] }))
.reduce((p, c) => ({ ...p, ...c }), {});
expect(() => {
model = new set.type(...Object.values(set.args));
}).not.toThrow();
expect(model).toBeDefined();
checkKeys.forEach((key) => expect(model[key]).toEqual(checkValues[key]));
});
});
});
The end result, for me, is:
You can use const obj = Object.create(MyClass.prototype) and then assign the values you want with Object.assign(obj, { foo: 'bar' })
This creates a class instance without using the new keyword or the constructor.

convert class to object actionscript 3

I`m tring to covert class to json.
The class is:
package com.globalData{
public class userSite {
private var uID:int,uName:String,uSocket:int,uZone:int,uRoom:int;
public function user(ID:int,Name:String,ZoneID:int,RoomID:int,socketID:int){
uID = ID;
uName = Name;
uSocket = socketID;
uZone = ZoneID;
uRoom = RoomID;
}
public function getName():String{
return uName;
}
public function getID():int{
return uID;
}
public function getZoneID():int{
return uZone;
}
public function getRoomID():int{
return uRoom;
}
public function getSocket():int{
return uSocket;
}
}
}
Im tryed to do:
json(Object(roomVar));
But its not work (JSOn is function on the main class)
Im need to convert the class to json and send the json -> Socket
How can i do it?
There are a few issues with your code above:
It doesn't appear as though your userSite class has a constructor. Instead, you've opted to have a user function that takes in all of the initialization arguments
You're using functions where you should probably be using accessor methods, sometimes called a getter.
public function getName():String { return uName;} would become public function get name():String { return uName;}
Instead of calling getName(), you would access name as a property: instance.name
You're attempting to pass an Object to the JSON.decode method, this method expects a String. Something like "{ 'a':1, 'b':[1,2,3] }" would be an acceptable parameter. This would return an object with two properties a and b, a would contain the value 1, and b would contain an array with the elements 1, 2, and 3. What you are looking for is actually the JSON.encode method which accepts an Object and converts it to a String (which can be parsed as JSON).
I suggest you convert all of your getXYZ() functions to accessors, this will allow an instance of that class to be read as a collection of properties, which will in turn allow the JSON.encode function to create a JSON string object from it:
package com.globalData
{
public class UserSite {
private var uID:int,uName:String,uSocket:int,uZone:int,uRoom:int;
public function UserSite(ID:int,Name:String,ZoneID:int,RoomID:int,socketID:int):void{
uID = ID;
uName = Name;
uSocket = socketID;
uZone = ZoneID;
uRoom = RoomID;
}
public function get name():String{
return uName;
}
public function get ID():int{
return uID;
}
public function get zoneID():int{
return uZone;
}
public function get roomID():int{
return uRoom;
}
public function get socket():int{
return uSocket;
}
}
}
Usage:
var roomVar:UserSite = new UserSite(1, 'Name', 2, 3, 4);
trace(JSON.encode(roomVar as Object));
Output:
{"ID":1,"name":"Name","socket":4,"roomID":3,"zoneID":2}