I'm using these tools together:
TypeScript
Gulp
Gulp-Inject
I'm trying to do the following:
module My {
interface IGulpInjectable extends string { // << Problem here!
[gulp_inject: string] : string;
}
export class Cache {
private items: { [key: string] : IGulpInjectable };
constructor() {
this.items = {
"item1": { gulp_inject: "file1.html" },
"item2": { gulp_inject: "file2.html" }
}
}
getItem(key: string){
return this.items[key].trim();
}
}
}
What gulp-inject does is replace { gulp_inject: "x.html" } with a string containing the file contents. This is why I want to have IGulpInjectable extend string: so that methods like trim() will be understood by TypeScript.
However, extends string is not valid. Neither is extends String. At least, not with my current constructor code, which I prefer not to change.
How can I tell TypeScript that my interface has all methods a string has?
Footnote, my current workaround:
getItem(key: string){
return (<any> this.items[key]).trim();
}
But that's not quite satisfying.
The following code works fine in typescript playground:
interface IGulpInjectable extends String
{
gulp_inject: string;
}
class Cache
{
private items: { [key: string] : IGulpInjectable };
constructor()
{
let item1 = new String(" 123 ");
item1["gulp_inject"] = "file1.html";
let item2 = new String(" 4556 ");
item2["gulp_inject"] = "file2.html";
this.items = {
"item1": <IGulpInjectable>item1,
"item2": <IGulpInjectable>item2
}
}
getItem(key: string)
{
return this.items[key];
}
}
let c = new Cache();
let i = c.getItem("item1");
console.log(i.trim()); //output '123'
console.log(i.gulp_inject); //output 'file1.html'
Link: typescript playground
Hope this helps.
Try changing to:
interface String extends String{
[gulp_inject: string] : string;
}
Related
I have an abstract class like:
export abstract class CsvFileReader<T> {
data: T[] = []
constructor(public file: string) {}
abstract mapRow(row: string[]): T
read() {
this.data = this.file
.split('\n')
.map((row: string): string[] => {
return row.split(',')
})
.map(this.mapRow)
}
}
and a class that extends above abstract class:
type matchData = [Date, string, string, number, number, MatchResualts, string]
export class MatchReader extends CsvFileReader<matchData> {
mapRow(row: string[]): matchData {
return [
dateStringToDate(row[0]),
row[1],
row[2],
+row[3],
+row[4],
row[5] as MatchResualts,
row[6],
]
}
}
I create reader just like this:
const reader = new MatchReader(Matches)
but i get this error: Generic type 'CsvFileReader' requires 1 type argument(s).
any solution?
Sorry my first answer was wrong.
you need to do something like this: const reader: CsvFileReader<matchData> = new MatchReader(Matches)
Does this work?
I'm porting some code to TypeScript, and a bit stumped by this error. The SomeObject type is supposed to allow an object with any named keys which equate to string values. When using in unpacked argument fields, I get two errors:
'SomeObject' is declared but its value is never read.
and
Cannot find name 'someArg'. Did you mean the instance member 'this.someArg'?
Here's the code:
type SomeObject = { [key: string]: string; }
class SomeClass {
someArg: SomeObject
constructor ({someArg: SomeObject} = {}) {
this.someArg = someArg
}
}
module.exports = SomeClass;
Here you can see where TypeScript has a problem in VS Code:
Am I missing something? I would expect to be able to create an instance of this class like so:
new SomeClass({someArg: {hello: 'world'}}) // Passes TypeScript
new SomeClass({someArg: {hello: 1}}) // Fails TypeScript as value is not a string
You need to declare your type in this way:
type SomeObject = { [key: string]: string; }
class SomeClass {
someArg: SomeObject
constructor({ someArg }: { [key: string]: SomeObject } = {}) {
this.someArg = someArg
}
}
Since someArg is optional, you should use indexed type, or you can use Record<string, SomeObject> instead of { [key: string]: SomeObject }
UPDATE
type SomeObject = { [key: string]: string; }
class SomeClass {
someArg: SomeObject | undefined
constructor({ someArg }: { someArg?: SomeObject } & Record<PropertyKey, any> = {}) {
this.someArg = someArg
}
}
const x = new SomeClass({}) // ok
const y = new SomeClass({ age: 42 }).someArg // ok
const z = new SomeClass({ someArg: { age: '42' } }).someArg // ok
More about destructuring in Typescript you can find in this article
Remove you {} and use...
type SomeObject = { [key: string]: string; }
class SomeClass {
someArg: SomeObject
constructor (someArg: SomeObject = {}) {
this.someArg = someArg
}
}
class fred {
fredJnr:SomeClass = new SomeClass( { Hello: "fred"})
}
module.exports = SomeClass;
Does that work for your use case?
let's assume we have the following situation:
class BaseClass {
// ....some logic
}
class A extends BaseClass{
name: string;
constructor(name: string) {
this.name = name
}
someMethodFromInside() {
// ....some logic
}
}
class B extends BaseClass {
name: string;
constructor(name: string) {
this.name = name
}
someMethodFromInside() {
// ....some logic
}
}
And then i would like to choose which object of a class i'm creating, based on some string value, that i'm getting from outside:
const someStringFromOutside = 'key1'
const obj: IHandler = {
'key1': A,
'key2': B
}
try {
// here i would like to create object based on value in string choose to pick up class and create
// object
new obj[someStringFromOutside]('somevariable').someMethodFromInside()
} catch(err) {
...some error handling
}
Because i'm writing in TypeScript i also have to know how to properly type interface in handler:
I was trying to pass some kind ObjectConstructor for value in interface. But it was to generic and typescript display me error.
enum HandlerEvents {
KEY_1 = 'key1',
KEY_2 = 'key2'
}
interface IHandler {
[key in HandlerEvents]: .....what should be here ?
}
I am trying to serialize/deserialize an object. I am thinking that the best way would be to save the path to the file that called as part of the json, but I am unsure of how to get said path.
Can getting this path to the file (A.ts/B.ts) be done when called within the parent (Base.ts)?
Is there maybe a better approach to doing this? I am trying to take a class created in the main node process, and and pass it to a worker process, the only why to do this that I can see is to serialize/deserialize the class somehow.
// src/Base.ts
export abstract class Base {
public serialize() {
return JSON.stringify({path: '', obj: this})
}
public static deserialize(json: string) {
let { path, obj } = JSON.parse(json) as { path: string, obj: { [key: string]: any } }
let newable = require(path)
let o = new newable
return Object.assign(o, obj)
}
}
// src/filter/A.ts
export class A extends Base {
public cat: string = 'meow'
public sayHi() { return this.cat }
}
// src/filter/B.ts
export class B extends Base {
public dog: string = 'woof'
public sayHi() { return this.dog }
}
// test.ts
let serializedA = new A().serialize()
let serializedB = new B().serialize()
// Create child...
let worker = cp.fork(path.join(__dirname, './worker'), [], { silent: true })
worker.send({ serializedA, serializedB })
// worker.ts
process.on('message', msg => {
let classA = Base.deserialize(msg.serializedA)
let classB = Base.deserialize(msg.serializedB)
})
The simplest way that comes to mind would be to have a set of class names associated with callbacks that would require the appropriate classes.
// src/JsonIO.ts
export class JsonIO {
private _classes: { name: string, callback: () => { new(): any } }[] = []
public serialize(obj: any): string {
return JSON.stringify({ class: obj.constructor.name, value: obj })
}
public deserialize(json: string) {
const obj = JSON.parse(json) as { class: string, value: any }
const clazz = this._classes.find(c => c.name == obj.class)
if(!clazz) return obj.value
return Object.assign(new (clazz.callback()), obj.value)
}
public registerClass(name: string, callback: () => { new(): any }) {
this._classes.push({ name, callback })
}
}
// src/Base.ts
export abstract class Base { /* ... */ }
// src/filter/A.ts
export class A {
public cat: string = 'meow'
}
// src/filter/B.ts
export class B {
public dog: string = 'woof'
}
// test.ts
const io = new JsonIO()
io.registerClass('A', () => A /* require('filter/A.ts') */)
io.registerClass('B', () => B /* require('filter/B.ts') */)
const serializedA = io.serialize(new A)
const serializedB = io.serialize(new B)
const a = io.deserialize(serializedA)
const b = io.deserialize(serializedB)
This is a typescript class with an exposed but not editable attribute:
export class Category {
private _id: number;
constructor(id: number){
this._id = id;
}
public get id(){
return this._id;
}
}
I would like to map it from a JSON like this:
{ id: 1 }
There are some obvious problems here:
I know that "_id" can't be magically mapped to "id" so maybe I could implement a custom logic that renames all the attributes with a _ before the name
I would like to keep the constructor with the id param but maybe I'm not able to map an object who require arguments before instantiation
With an empty constructor, I tried Object.assign(new Category(), jsonObject), however, this does not work since Cannot set property id of #<Category> which has only a getter
What I want to avoid is to write custom mapping logic for every class like this with private attributes, I tried some other solutions and libraries but didn't work for my situation, they're all referencing to class with only public attributes
I don't even know if what I ask is achievable, so if the case it isn't, then I will "surrender" to use the class with only public attributes
The missconception here is that you need a getter/setter at all just to manage visibility. You can't prevent code from accessing and modifying id, no matter what you do. You can however tell the IDE (and the developer using it) that he can only read/get the property by using the readonly modifier, which simplifies your code to:
export class Category {
readonly id: number;
}
Noe that readonly thing only exists at compile time, and doesnt have any affects at runtime. Your IDE will prevent you from doing:
(new Category).id = 5;
But it allows you to easily do:
const category = Object.assign(new Category, { id: 5 });
Pass the object through constructor:
export class Category {
private _a: number;
private _b: string;
constructor(values: { a: number; b: string }){
this._a = values.a;
this._b = values.b;
}
public getA() { return this._a; }
public getB() { return this._b; }
}
You can still call it with or without JSON:
let cat1 = Category (values: { a: 42, b: 'hello' });
let json = '{"a":42,"b":"hello"}'
let cat2 = Category (values: JSON.parse (json));
Alternatively, keep the values in an object rather than a direct member. This makes it unnecessary to map the object to member variables:
export class Category {
private _values: {
a: number;
b: string;
}
constructor(values: { a: number; b: string }){
this._values = values;
}
public getA() { return this._values.a; }
public getB() { return this._values.b; }
}
If you want to keep the old constructor, you can do so like this:
export class Category {
private _values: {
a: number;
b: string;
}
constructor(a: number, b: string) {
this._values = { a: a, b: b };
}
public static make (values: { a: number; b: string }) {
this._values = values;
}
public getA() { return this._values.a; }
public getB() { return this._values.b; }
}