Declare dynamically added class properties in TypeScript - class

I want to assign properties to the instance of a class without knowing the property names, values and types of values in TypeScript. Lets assume we have the following example.ts script:
// This could be a server response and could look totally diffent another time...
const someJson:string = '{ "foo": "bar", "bar": "baz" }'
class MyClass {
someProperty:boolean
constructor( json:string ) {
const parsedJson:any = JSON.parse( json )
Object.keys( parsedJson ).forEach(
( key:string ) => {
this[ key ] = parsedJson[ key ]
}
)
this['someProperty'] = true
}
}
const myInstance = new MyClass( someJson )
// Works fine, logs `true`.
console.log( myInstance.someProperty )
// Error: Property 'foo' does not exist on type 'MyClass'.
console.log( myInstance.foo )
// Error: Property 'bar' does not exist on type 'MyClass'.
console.log( myInstance.bar )
How can I make sure that the TypeScript compiler does not complain of the dynamically added properties but instead handle them as "key": value pairs of any type. I still want tsc to make sure that myInstance.someProperty has to be of type boolean but I want to be able to get myInstance.whatever even if it is not defined without running into compiler errors.
I did not find any documentation that makes this clear to me. Maybe because I'm not a native english speaker. So please keep the answers simple.
Edit:
I remember that there was something like the following but I never got that to work:
interface IMyClass {
[name:string]: any
}

The problem is that you're adding the new properties at runtime and the compiler has no way of knowing that.
If you know the property names in advance then you can do this:
type Json = {
foo: string;
bar: string;
}
...
const myInstance = new MyClass(someJson) as MyClass & Json;
console.log(myInstance.foo) // no error
Edit
If you do not know the properties in advance then you can't do this:
console.log(myInstance.foo);
Because then you know that foo is part of the received json, you'll probably have something like:
let key = getKeySomehow();
console.log(myInstance[key]);
And this should work without an error from the compiler, the only problem with that is that the compiler doesn't know the type for the returned value, and it will be any.
So you can do this:
const myInstance = new MyClass(someJson) as MyClass & { [key: string]: string };
let foo = myInstance["foo"]; // type of foo is string
let someProperty = myInstance["someProperty"]; // type of someProperty is boolean
2nd edit
As you do know the props, but not in the class, you can do:
type ExtendedProperties<T> = { [P in keyof T]: T[P] };
function MyClassFactory<T>(json: string): MyClass & ExtendedProperties<T> {
return new MyClass(json) as MyClass & ExtendedProperties<T>;
}
Then you simply use it like so:
type Json = {
foo: string;
bar: string;
};
const myInstance = MyClassFactory<Json>(someJson);
Note that this will work only on typescript 2.1 and above.

If you want to dynamically add class properties via an object upon instantiation, and type information is available for that object, you can very nicely get full type safety in this way (as long as you don't mind using a static factory method):
class Augmentable {
constructor(augment: any = {}) {
Object.assign(this, augment)
}
static create<T extends typeof Augmentable, U>(this: T, augment?: U) {
return new this(augment) as InstanceType<T> & U
}
}
This is using the (fake) this parameter to infer the constructor type of the class. It then constructs the instance, and casts it to a union of the instance type (using the InstanceType utility type) and the inferred type of the props you passed to the method.
(We could have casted directly to Augmentable & U, however this way allows us to extend the class.)
Examples
Augment basic properties:
const hasIdProp = Augmentable.create({ id: 123 })
hasIdProp.id // number
Augment with methods:
const withAddedMethod = Augmentable.create({
sayHello() {
return 'Hello World!'
}
})
withAddedMethod.sayHello() // Properly typed, with signature and return value
Extend and augment, with this access in method augments:
class Bob extends Augmentable {
name = 'Bob'
override = 'Set from class definition'
checkOverrideFromDefinition() {
return this.override
}
}
interface BobAugment {
whatToSay: string
override: string
sayHelloTo(to: string): void
checkOverrideFromAugment(): string
}
const bobAugment: BobAugment = {
whatToSay: 'hello',
override: 'Set from augment'
sayHelloTo(this: Bob & BobAugment, to: string) {
// Let's combine a class parameter, augment parameter, and a function parameter!
return `${this.name} says '${this.whatToSay}' to ${to}!`
},
checkOverrideFromAugment(this: Bob & BobAugment) {
return this.override
}
}
const bob = Bob.create(bobAugment) // Typed as Bob & BobAugment
bob.sayHelloTo('Alice') // "Bob says 'hello' to Alice!"
// Since extended class constructors always run after parent constructors,
// you cannot override a class-set parameter with an augment, no matter
// from where you are checking it.
bob.checkOverrideFromAugment() // "Set from class definition"
bob.checkOverrideFromDefinition() // "Set from class definition"
Limitations
Augmented properties aren't really part of the class, so you can't extend a class with those augments included. This may be a feature for some use cases where the augments are temporary additions that aren't meant to modify the prototype hierarchy
It is also not easy to add non-augment arguments to .create(), however an easy work-around is to simply utilize the augment functionality to accomplish the same thing you would have with an extra argument.

You can add index signature to your class:
class MyClass {
[index: string]: any; //index signature
someProperty:boolean
constructor( json:string ) {
const parsedJson:any = JSON.parse( json )
Object.keys( parsedJson ).forEach(
( key:string ) => {
this[ key ] = parsedJson[ key ]
}
)
this['someProperty'] = true
}
}

Related

Value get is not a member of java.io.Serializable

I have written 2 codes .The functionality of both the code is same.Both the codes take user data then store it in map and on providing keys we get correspoding user data. I have written an extra logic in code2, whic I have mentioned below.
Code1:
class user(var name:String,var id:Int, var gender:Option[String])
{
override def toString="("+ name+","+id+","+gender+")"
}
object a
{
def main(args:Array[String]):Unit={
var a=new user("kl",90,Some("Male"))
println(a.name,a.id,a.gender)//ACESS VALUES
//DEFINING MAP
var mm=Map(1-> new user("jh",189,Some("Male")),2->new user("gh",12,None),3
->new user("io",100,Some("Female")))
// defining method giving o/p value of specific key of mm
def getkey(i:Int)=
{ mm.get(i)
}
var u1=getkey(readLine("ENTER THE KEY").toInt) // some[user]
println(u1.getClass.getName)
if(u1.isDefined)
{
println(u1.get+","+u1.get.name+","+u1.get.id+","+u1.get.gender)
}
}
}
Code1 1 works properly and O/P is right. I have added extra logic in Code2. The extra logic is getKey method. I have written a code for checking whether the input key is present in map. There I am getting an error:
**value get is not a member of java.io.Serializable**_
Code2:
class user(var name:String,var id:Int, var gender:Option[String])
{
override def toString="("+ name+","+id+","+gender+")"
}
object a
{
def main(args:Array[String]):Unit={
var a=new user("kl",90,Some("Male"))
println(a.name,a.id,a.gender)//ACESS VALUES
//DEFINING MAP
var mm=Map(1-> new user("jh",189,Some("Male")),2->new user("gh",12,None),3-> new user("io",100,Some("Female")))
// defining method giving o/p value of specific key of mm
def getkey(i:Int)=
{
//EXTRA LOGIC
var a=(mm.keys).toList
if(a.contains(i)){mm.get(i)}
else {"NO SUCH ELEMENT EXCEPTION , KEY DOESNT MATCH"}
}
print("ENTER THE KEY \n")
var u1=getkey(1) // some[user]
println(u1.get)
}
}
ERROR -
enter code here
eg1.Option.scala:27: error: value get is not a member of
java.io.Serializable
println(u1.get)
^
one error found
Why does the seriliazable errors occurs in Code2 and not in Code1? Is the error due extra logic in Code2? How to fix an error?
Thank you!
It happens because your getKey function return type is io.Serializable.
Reason for this is that every branch of your if expression is returning a different type:
def getkey(i:Int) = { // io.Serializable
//EXTRA LOGIC
var a=(mm.keys).toList
if(a.contains(i)) { mm.get(i) } // option here
else { "NO SUCH ELEMENT EXCEPTION , KEY DOESNT MATCH" } // string here
}
Consider rewriting your function, so its return type is Option[User], one way of doing so is:
def getkey(i:Int): Option[user] = {
//EXTRA LOGIC
var a=(mm.keys).toList
if(a.contains(i)) { mm.get(i) }
else { None }
}
However, there is no need for checking keys, you can simplify this function to:
def getkey(i:Int): Option[user] = {
//EXTRA LOGIC
m.get(i)
}
Hint: write expected return type for functions to see what's going wrong in such cases.

TypeScript generic class that creates instances of its type variable's class?

I'm familiar with generic classes in TypeScript, where a class can be defined with an associated type variable, and then instances with a specific type can manipulate and return values of that type.
Problem: I want a generic class that creates instances of the type variable. For instance:
class Thing {
thingProp: string;
}
class ThingOne extends Thing {
thingOneProp: string;
}
class ThingTwo extends Thing {
thingTwoProp: string;
}
class Maker<T extends Thing> {
make(): T {
return new T();
// ^--- " // <- "error TS2304: Cannot find name 'T'""
}
}
let thingOneMaker = new Maker<ThingOne>();
let thingOne: ThingOne = thingOneMaker.make();
let thingTwoMaker = new Maker<ThingTwo>();
let thingTwo: ThingTwo = thingTwoMaker.make();
let thingError: ThingOne = thingTwoMaker.make();
// ^--- "error TS2322: Type 'ThingTwo' is not assignable to type 'ThingOne'"
This almost seems to work. The compiler generates code, and the error on the last line shows that TypeScript understands what type thingTwoMaker.make() should return.
However, the error on return new T(); shows that TypeScript doesn't understand that I'm trying to make instances of the type variable's class, and the generated JavaScript confirms it:
var Maker = (function () {
function Maker() {
}
Maker.prototype.make = function () {
return new T(); // <- "error TS2304: Cannot find name 'T'"
};
return Maker;
}());
And, not surprisingly, running the generated JavaScript with Node.js produces a ReferenceError: T is not defined error.
How can I make a generic class whose instances can create instances of the type variable class? (Tested using TypeScript 2.0.10.)
Somehow, you need to give the Maker class the constructor of the type you'd like it to make, so that it has a value on which to call new.
I'd say a good option would be to pass the class constructor as an argument to Maker's constructor. That will allow it to construct instances of that class, and it will automatically infer the type that it's building so you don't have to manually annotate the generic type anymore.
So maybe something like this:
class Maker<T extends Thing> {
private ctor: {new(): T};
constructor(ctor: {new(): T}) {
this.ctor = ctor;
}
make(): T {
return new this.ctor();
}
}
Then, you can pass the right class constructor to each kind of Maker, and the type will be automatically inferred:
let thingOneMaker = new Maker(ThingOne);
let thingOne: ThingOne = thingOneMaker.make();
let thingTwoMaker = new Maker(ThingTwo);
let thingTwo: ThingTwo = thingTwoMaker.make();
// Still an error.
let thingError: ThingOne = thingTwoMaker.make();
Playground link.

Unify generic types in macro

I wanted to use macro to check if a function is returning a particular generic type, say Array, so it is fine if the function is returning Array<Dynamic>, Array<String>, or even generic Array<T>.
So I tried to Context.unify it with Array<Dynamic>. It is fine for Array<String> or Array<Dynamic> but it fails when the type parameter is "generic" because the ComplexType Array<T> won't convert to a Type with Type not found: T (See code below). Are there any possible ways to achieve what I am attempting to do?
package;
#if macro
import haxe.macro.Context;
using haxe.macro.ComplexTypeTools;
#end
#if !macro #:build(Macros.build()) #end
class Main
{
public function test<T>():Array<T>
{
return [];
}
}
class Macros
{
public static function build()
{
#if macro
var fields = Context.getBuildFields();
for(field in fields)
{
switch(field.kind)
{
case FFun(f):
// try to unify Array<String> with Array<Dynamic>
trace(Context.unify((macro:Array<String>).toType(), (macro:Array<Dynamic>).toType()));
// true
// try to unify Array<T> with Array<Dynamic>
trace(Context.unify(f.ret.toType(), (macro:Array<Dynamic>).toType()));
// Type not found: T
default:
}
}
return null;
#end
}
}
UPDATE
So, checking TPath was not the best idea.
Based on the previous assumption about Dynamic being assignable to any type we can replace unconvertable type parameter with the Dynamic (eg Array<T> = Array<Dynamic>) and when try to unify it.
static function toTypeExt(c:ComplexType):Null<haxe.macro.Type>
{
try {
return c.toType();
} catch (error:Error)
{
switch (c)
{
case TPath(p):
//deep copy of the TPath with all type parameters as Dynamic
var paramsCopy = [for (i in 0...p.params.length) TPType(macro:Dynamic)];
var pCopy:TypePath = {
name: p.name,
pack: p.pack,
sub: p.sub,
params: paramsCopy
}
var cCopy = TPath(pCopy);
//convert after
return cCopy.toType();
default:
}
}
return null;
}
Use toTypeExt() in your build macro instead of toType.
trace(Context.unify(toTypeExt(f.ret), (macro:Array<Dynamic>).toType()));
Looks more like a workaround to me, but there is a strange thing about ComplexTypeTools.toType - it will succeed with a class type parameter while failing with method type parameter.
OLD ANSWER
Unification won't work since there is no way of converting ComplexType with the type parameter to Type (in that context). But since you are unifying with Array it is safe to assume that any Array will unify with it (since any type is assignable to Dynamic http://haxe.org/manual/types-dynamic.html).
May be it is not the pritiest solution, but simple TPath check is the way to go here:
case FFun(f):
switch (f.ret) {
case TPath({ name: "Array", pack: [], params: _ }):
trace("Yay!");
default:
}

Cast class type retrieved from string

I'm trying to read a class from a String and pass its type to a generic function. But it seems like there's no way to achieve this:
protocol Person { ... }
class Student: Person { ... }
class Teacher: Person { ... }
func foo<SomePerson: Person>(param: String, type: SomePerson.Type) { ... }
// Get class from string and pass class type to foo()
let someClass = NSClassFromString("MyApp.Teacher")
foo("someParam", type: someClass.dynamicType)
Trying this I'm getting an error:
Cannot invoke 'foo' with an argument list of type '(String, type: AnyClass.Type)
Is it actually possible to obtain the 'real' type 'Teacher' instead of the generic type 'AnyClass' and pass it to foo()? The classes are read on runtime from a file - so using hard class names when calling foo() is not possible.
Thanks for any advice!
If Person is a class as the original question was written:
Since you are making the life for the compiler considerably harder that it needs to be you have to help him a bit. You have to make sure the type is correct before calling the function:
if let actualType = someClass as? Person.Type {
foo("someParam", type: actualType)
} else {
// error handling here
}
Which yields the desired working code:
If Person is a protocol:
You are going to have a bad day. The above solution will not work. As far as I can remember your only option is to check for every possible subclass:
if let actualType = someClass as? Student.Type {
foo("someParam", type: actualType)
} else if let actualType = someClass as? Teacher.Type {
foo("someParam", type: actualType)
}
// etc.

Generic Types Collection

Building on previous question which got resolved, but it led to another problem. If protocol/class types are stored in a collection, retrieving and instantiating them back throws an error. a hypothetical example is below. The paradigm is based on "Program to Interface not an implementation" What does it mean to "program to an interface"?
instantiate from protocol.Type reference dynamically at runtime
public protocol ISpeakable {
init()
func speak()
}
class Cat : ISpeakable {
required init() {}
func speak() {
println("Meow");
}
}
class Dog : ISpeakable {
required init() {}
func speak() {
println("Woof");
}
}
//Test class is not aware of the specific implementations of ISpeakable at compile time
class Test {
func instantiateAndCallSpeak<T: ISpeakable>(Animal:T.Type) {
let animal = Animal()
animal.speak()
}
}
// Users of the Test class are aware of the specific implementations at compile/runtime
//works
let t = Test()
t.instantiateAndCallSpeak(Cat.self)
t.instantiateAndCallSpeak(Dog.self)
//doesn't work if types are retrieved from a collection
//Uncomment to show Error - IAnimal.Type is not convertible to T.Type
var animals: [ISpeakable.Type] = [Cat.self, Dog.self, Cat.self]
for animal in animals {
//t.instantiateAndCallSpeak(animal) //throws error
}
for (index:Int, value:ISpeakable.Type) in enumerate(animals) {
//t.instantiateAndCallSpeak(value) //throws error
}
Edit - My current workaround to iterate through collection but of course it's limiting as the api has to know all sorts of implementations. The other limitation is subclasses of these types (for instance PersianCat, GermanShepherd) will not have their overridden functions called or I go to Objective-C for rescue (NSClassFromString etc.) or wait for SWIFT to support this feature.
Note (background): these types are pushed into array by users of the utility and for loop is executed on notification
var animals: [ISpeakable.Type] = [Cat.self, Dog.self, Cat.self]
for Animal in animals {
if Animal is Cat.Type {
if let AnimalClass = Animal as? Cat.Type {
var instance = AnimalClass()
instance.speak()
}
} else if Animal is Dog.Type {
if let AnimalClass = Animal as? Dog.Type {
var instance = AnimalClass()
instance.speak()
}
}
}
Basically the answer is: correct, you can't do that. Swift needs to determine the concrete types of type parameters at compile time, not at runtime. This comes up in a lot of little corner cases. For instance, you can't construct a generic closure and store it in a variable without type-specifying it.
This can be a little clearer if we boil it down to a minimal test case
protocol Creatable { init() }
struct Object : Creatable { init() {} }
func instantiate<T: Creatable>(Thing: T.Type) -> T {
return Thing()
}
// works. object is of type "Object"
let object = instantiate(Object.self) // (1)
// 'Creatable.Type' is not convertible to 'T.Type'
let type: Creatable.Type = Object.self
let thing = instantiate(type) // (2)
At line 1, the compiler has a question: what type should T be in this instance of instantiate? And that's easy, it should be Object. That's a concrete type, so everything is fine.
At line 2, there's no concrete type that Swift can make T. All it has is Creatable, which is an abstract type (we know by code inspection the actual value of type, but Swift doesn't consider the value, just the type). It's ok to take and return protocols, but it's not ok to make them into type parameters. It's just not legal Swift today.
This is hinted at in the Swift Programming Language: Generic Parameters and Arguments:
When you declare a generic type, function, or initializer, you specify the type parameters that the generic type, function, or initializer can work with. These type parameters act as placeholders that are replaced by actual concrete type arguments when an instance of a generic type is created or a generic function or initializer is called. (emphasis mine)
You'll need to do whatever you're trying to do another way in Swift.
As a fun bonus, try explicitly asking for the impossible:
let thing = instantiate(Creatable.self)
And... swift crashes.
From your further comments, I think closures do exactly what you're looking for. You've made your protocol require trivial construction (init()), but that's an unnecessary restriction. You just need the caller to tell the function how to construct the object. That's easy with a closure, and there is no need for type parameterization at all this way. This isn't a work-around; I believe this is the better way to implement that pattern you're describing. Consider the following (some minor changes to make the example more Swift-like):
// Removed init(). There's no need for it to be trivially creatable.
// Cocoa protocols that indicate a method generally end in "ing"
// (NSCopying, NSCoding, NSLocking). They do not include "I"
public protocol Speaking {
func speak()
}
// Converted these to structs since that's all that's required for
// this example, but it works as well for classes.
struct Cat : Speaking {
func speak() {
println("Meow");
}
}
struct Dog : Speaking {
func speak() {
println("Woof");
}
}
// Demonstrating a more complex object that is easy with closures,
// but hard with your original protocol
struct Person: Speaking {
let name: String
func speak() {
println("My name is \(name)")
}
}
// Removed Test class. There was no need for it in the example,
// but it works fine if you add it.
// You pass a closure that returns a Speaking. We don't care *how* it does
// that. It doesn't have to be by construction. It could return an existing one.
func instantiateAndCallSpeak(builder: () -> Speaking) {
let animal = builder()
animal.speak()
}
// Can call with an immediate form.
// Note that Cat and Dog are not created here. They are not created until builder()
// is called above. #autoclosure would avoid the braces, but I typically avoid it.
instantiateAndCallSpeak { Cat() }
instantiateAndCallSpeak { Dog() }
// Can put them in an array, though we do have to specify the type here. You could
// create a "typealias SpeakingBuilder = () -> Speaking" if that came up a lot.
// Again note that no Speaking objects are created here. These are closures that
// will generate objects when applied.
// Notice how easy it is to pass parameters here? These don't all have to have the
// same initializers.
let animalBuilders: [() -> Speaking] = [{ Cat() } , { Dog() }, { Person(name: "Rob") }]
for animal in animalBuilders {
instantiateAndCallSpeak(animal)
}