How to reduce javascript object to only contain properties from interface - javascript

When using typescript a declared interface could look like this:
interface MyInterface {
test: string;
}
And an implementation with extra property could be like this:
class MyTest implements MyInterface {
test: string;
newTest: string;
}
Example (here the variable 'reduced' still contain the property 'newTest'):
var test: MyTest = {test: "hello", newTest: "world"}
var reduced: MyInterface = test; // something clever is needed
Question
In a general way, how can you make the 'reduced' variable to only contain the properties declared in the 'MyInterface' interface.
Why
The problem occur when trying to use the 'reduced' variable with angular.toJson before sending it to a rest service - the toJson method transforms the newTest variable, even if it's not accessible on the instance during compile, and this makes the rest service not accept the json since it has properties that shouldn't be there.

It is not possible to do this. The reason being interface is a Typescript construct and the transpiled JS code is empty
//this code transpiles to empty!
interface MyInterface {
test: string;
}
Thus at runtime there is nothing to 'work with' - no properties exist to interrogate.
The answer by #jamesmoey explains a workaround to achieve the desired outcome.
A similar solution I use is simply to define the 'interface' as a class -
class MyInterface {
test: string = undefined;
}
Then you can use lodash to pick the properties from the 'interface' to inject into you object:
import _ from 'lodash'; //npm i lodash
const before = { test: "hello", newTest: "world"};
let reduced = new MyInterface();
_.assign(reduced , _.pick(before, _.keys(reduced)));
console.log('reduced', reduced)//contains only 'test' property
see JSFiddle
This is a pragmatic solution that has served me well without getting bogged down on semantics about whether it actually is an interface and/or naming conventions (e.g. IMyInterface or MyInterface) and allows you to mock and unit test

TS 2.1 has Object Spread and Rest, so it is possible now:
var my: MyTest = {test: "hello", newTest: "world"}
var { test, ...reduced } = my;
After that reduced will contain all properties except of "test".

Another possible approach:
As other answers have mentioned, you can't avoid doing something at runtime; TypeScript compiles to JavaScript, mostly by simply removing interface/type definitions, annotations, and assertions. The type system is erased, and your MyInterface is nowhere to be found in the runtime code that needs it.
So, you will need something like an array of keys you want to keep in your reduced object:
const myTestKeys = ["test"] as const;
By itself this is fragile, since if MyInterface is modified, your code might not notice. One possible way to make your code notice is to set up some type alias definitions that will cause a compiler error if myTestKeys doesn't match up with keyof MyInterface:
// the following line will error if myTestKeys has entries not in keyof MyInterface:
type ExtraTestKeysWarning<T extends never =
Exclude<typeof myTestKeys[number], keyof MyInterface>> = void;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type 'UNION_OF_EXTRA_KEY_NAMES_HERE' does not satisfy the constraint 'never'
// the following line will error if myTestKeys is missing entries from keyof MyInterface:
type MissingTestKeysWarning<T extends never =
Exclude<keyof MyInterface, typeof myTestKeys[number]>> = void;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type 'UNION_OF_MISSING_KEY_NAMES_HERE' does not satisfy the constraint 'never'
That's not very pretty, but if you change MyInterface, one or both of the above lines will give an error that hopefully is expressive enough that the developer can modify myTestKeys.
There are ways to make this more general, or possibly less intrusive, but almost no matter what you do, the best you can reasonably expect from TypeScript is that your code will give compiler warnings in the face of unexpected changes to an interface; not that your code will actually do different things at runtime.
Once you have the keys you care about you can write a pick() function that pulls just those properties out of an object:
function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
return keys.reduce((o, k) => (o[k] = obj[k], o), {} as Pick<T, K>);
}
And them we can use it on your test object to get reduced:
var test: MyTest = { test: "hello", newTest: "world" }
const reduced: MyInterface = pick(test, ...myTestKeys);
console.log(JSON.stringify(reduced)); // {"test": "hello"}
That works!
Playground link to code

Are you trying to only set/assign properties listed on the interface only? Functionality like that is not available in TypeScript but it is very simple to write a function to perform the behaviour you looking for.
interface IPerson {
name: string;
}
class Person implements IPerson {
name: string = '';
}
class Staff implements IPerson {
name: string = '';
position: string = '';
}
var jimStaff: Staff = {
name: 'Jim',
position: 'Programmer'
};
var jim: Person = new Person();
limitedAssign(jimStaff, jim);
console.log(jim);
function limitedAssign<T,S>(source: T, destination: S): void {
for (var prop in destination) {
if (source[prop] && destination.hasOwnProperty(prop)) {
destination[prop] = source[prop];
}
}
}

In your example newTest property won't be accessible thru the reduced variable, so that's the goal of using types. The typescript brings type checking, but it doesn't manipulates the object properties.

In a general way, how can you make the 'reduced' variable to only contain the properties declared in the 'MyInterface' interface.
Since TypeScript is structural this means that anything that contains the relevant information is Type Compatible and therefore assignable.
That said, TypeScript 1.6 will get a concept called freshness. This will make it easier to catch clear typos (note freshness only applies to object literals):
// ERROR : `newText` does not exist on `MyInterface`
var reduced: MyInterface = {test: "hello", newTest: "world"};

Easy example:
let all_animals = { cat: 'bob', dog: 'puka', fish: 'blup' };
const { cat, ...another_animals } = all_animals;
console.log(cat); // bob

One solution could be to use a class instead of an interface and use a factory method (a public static member function that returns a new object of it's type). The model is the only place where you know the allowed properties and it's the place where you don't forget to update them accidentaly on model changes.
class MyClass {
test: string;
public static from(myClass: MyClass): MyClass {
return {test: myClass.test};
}
}
Example:
class MyTest extends MyClass {
test: string;
newTest: string;
}
const myTest: MyTest = {test: 'foo', newTest: 'bar'};
const myClass: MyClass = MyClass.from(myTest);

Related

Get Javascript interface keys as string (like an Enum)

My request is actually really simple but I have found almost nothing of the sort online. Picture this, you have an interface like so:
interface MyInterface: {
propertyA: number;
propertyB: string;
propertyC: boolean;
}
Now you want to use the name of a field to access its value dynamically so you do something like object['propertyA'], where object is of type MyInterface.
However, passing 'propertyA' as a raw string is impractical and dangerous because if you rename it or change the interface the code won't work. (And it doesn't get picked up by typescript but I'm not sure this is possible anyway).
Ideally, I would like to do something like this MyInterface.propertyA, I was saddened that this doesn't seem to exist, I don't see any reason why it shouldn't.
And now before you say that's what object.propertyA is for, I have simplified the concept to make it easy to understand but the use case is not exactly this. I might pass fields of an interface as strings from react components within the JSX to make use of a reusable method or I could be checking whether a string matches a subset of keys in an interface to display errors accordingly.
Autogenerating an enum from the interface/class is not a bad solution although not ideal as it still incurs an overhead but as long as it doesn't need to be updated each time the interface changes and it doesn't depend on the type of the interface's properties then that's not bad.
Interfaces don't exist at runtime, so there's no way to get the property names of an interface as runtime values. What you can do, though, is go the other way: Have a "model" object:
const myInterfaceModel = {
propertyA: 42,
propertyB: "x",
propertyC: true,
};
Then define the interface using that object:
type MyInterface = typeof myInterfaceModel;
That defines MyInterface just as it is in your question.
Then you can create an object that has the property names as strings, like this:
const myInterfaceKeys = Object.fromEntries(
Object.keys(myInterfaceModel).map((key) => [key, key])
);
Now, if we just did that, myInterfaceKeys would be (effectively) Record<string, string>, which isn't useful for devex. But we can do better by using a mapped type on it:
type KeyNames<T> = {
[Key in keyof T]: Key;
};
// ...
const myInterfaceKeys = Object.fromEntries(
Object.keys(myInterfaceModel).map((key) => [key, key])
) as KeyNames<MyInterface>;
Now, its type matches its runtime value, and we get useful autocompletion:
Example usage (just a basic function, but could as easily be a React component):
function example(obj: MyInterface, key: keyof MyInterface) {
console.log(obj[key]);
}
declare let exampleObject: MyInterface;
example(exampleObject, myInterfaceKeys.propertyA);
Here's that all together (playground link):
type KeyNames<T> = {
[Key in keyof T]: Key;
};
const myInterfaceModel = {
propertyA: 42,
propertyB: "x",
propertyC: true,
};
type MyInterface = typeof myInterfaceModel;
// ^? type MyInterface = { propertyA: number, propertyB: string; propertyC : boolean }
const myInterfaceKeys = Object.fromEntries(
Object.keys(myInterfaceModel).map((key) => [key, key])
) as KeyNames<MyInterface>;
myInterfaceKeys
// ^−−−− Type a period after this to see the autocompletion
Usually I'd just use string literals (example(exampleObject, "propertyA")) since the IDE will offer the appropriate choices, and since keyof MyInterface is restrictive, if you remove a property, any code that referenced it with a string literal would stop compiling. But if you want that object with the keys on it, you can do the above to achieve that. (Also, I just tried, and if you use your object of keys, renaming a property works throughout, whereas the string literals didn't.)
Are you looking for something like this?
enum Direction {
Up = "u",
Down = "d",
Left = "l",
Right = "r",
}
interface MyDirection {
[Direction.Up]:string;
[Direction.Down]:number;
}
const obj:MyDirection = {
[Direction.Up]: 'up',
[Direction.Down]:12,
}
console.log(obj[Direction.Down], Direction.Down)

Typescript - Extending class with type

Typescript newbie here.
Let's say I have a type coming from a library that looks like this:
type FooType {
name: string;
// Has much more attributes in real life
}
I now want to define a class called Foo like this:
import { FooType } from 'my-library';
class Foo {
constructor(data: FooType) {
Object.assign(this, data);
}
}
With this code, I'm able to define a foo instance, yet I have an issue with autocomplete:
const foo = new Foo({ name: 'foo name' });
// Typing in foo.name does not bring any type information about the "name" attribute
Is there a way I can make a class automatically "inherit" all attributes from a type without having to type them manually?
Edit after being marked as duplicate:
What I want to achieve is to avoid manually typing attributes that are already existing on a type.
Thanks to #Phil I've been provided an answer that mentions this as an ongoing issue within Typescript:
https://github.com/microsoft/TypeScript/issues/26792
What I will do for now is the following:
class Foo {
constructor(public _data: FooType)
Object.assign(this, _data);
}
get() {
return this._data;
}
}
const foo = new Foo({ name: 'Bar' });
foo.get().name; // After typing "foo.get()" the autocomplete works properly.
Note: this is just a workaround and doesn't fix the underlying issue with using Object.assign() in the constructor with TypeScript.
https://github.com/Microsoft/TypeScript/issues/16672
Update: this question definitely lead me down quite the rabbit and I wasn't quite happy with my previous response so here is something I think would work better for solving these two items:
Creating a class which extends an object
Allowing TypeScript to still do its thing
Step One: use the typescript mixin pattern to return an anonymous class with the properties of a generic object T:
function GenericBase<T>(data: T) {
return class {
constructor() {
Object.assign(this, data)
}
} as ({
new (...args: any[]): T;
})
}
Now we have an anonymous function with a constructor which is then casted as a class that returns our generic object T.
Step Two: Note that we don't need to touch the above code we just need to extend it, so for OP's example we would do this:
class MyClass extends GenericBase(foo) {
constructor() {
super()
}
greet() {
return `Hello, ${this.name}!`
}
}
More examples:
const myClass = new MyClass()
console.log(myClass.name) // bar
console.log(myClass.greet()) // Hello, bar!
function doSomethingWithFoo(someFoo: Foo) {
// example method that only takes types Foo
}
doSomethingWithFoo(myClass)
Try it on the TypeScript playground!

TypeScript - How do you chain accessing optional nested type properties?

I have a Client class that stores caches of other objects that the application needs to keep in memory. The structure of the object's cache is developer-defined. For example, if we have a cache of Example objects:
class Example {
property1: string;
property2: string;
}
The developer might only want property1 cached.
import { EventEmitter } from "events";
// Constructor options for `Client`
interface ClientData {
cacheStrategies?: CacheStrategies;
}
// How various objects should be cached
interface CacheStrategies {
example?: ExampleCacheStrategies;
...
}
// Metadata for how each type of object should be cached
interface ExampleCacheStrategies {
cacheFor?: number;
customCache?: ExampleCustomCacheData;
}
// The custom structure of what parts of `example` objects should be cached
interface ExampleCustomCacheData {
property1?: boolean;
property2?: boolean;
}
// The object stored in `Client.exampleCache`, based on the custom structure defined in `ExampleCustomCacheData`
interface CustomExampleData<CachedExampleProperties extends ExampleCustomCacheData> {
property1: CachedExampleProperties["property1"] extends true ? string /* 1 */ : undefined;
property2: CachedExampleProperties["property2"] extends true ? string : undefined;
}
class Client<ClientOptions extends ClientData> extends EventEmitter {
// The map's value should be based on the custom structure defined in `ExampleCustomCacheData`
exampleCache: Map<string, CustomExampleData<ClientOptions["cacheStrategies"]["example"]["customCache"]>>;
constructor(clientData: ClientOptions) {
super();
this.exampleCache = new Map();
}
}
const client = new Client({
cacheStrategies: {
example: {
/**
* The properties of `example` objects that should be cached
* This says that `property1` should be cached (string (1))
*/
customCache: {
property1: true, // (2)
... // (3)
}
}
}
});
client.exampleCache.set("123", {
property1: "value"
});
const exampleObject = client.exampleCache.get("123");
if (exampleObject) {
// Should be `string` instead of `string | undefined` (2)
console.log(exampleObject.property1);
// `string | undefined`, as expected since it's falsey (3)
console.log(exampleObject.property2);
}
As explained in the comments above the console.log()s, the goal is for objects that are pulled from the cache to have property1 be a string instead of string | undefined.
The problem is that exampleCache: Map<string, CustomExampleData<ClientOptions["cacheStrategies"]["example"]["customCache"]>>; doesn't work since both ClientOptions["cacheStrategies"] and ClientOptions["cacheStrategies"]["example"] are optional. The following doesn't work either:
exampleCache: Map<string, CustomExampleData<ClientOptions["cacheStrategies"]?.["example"]?.["customCache"]>>;
It errors with '>' expected at ?.. How can I solve this?
Syntax like the optional chaining operator ?. or the non-null assertion operator ! only applies to value expressions that will make it through to JavaScript in some form. But you need something that works with type expressions which exist only in the static type system and are erased when transpiled.
There is a NonNullable<T> utility type which is the type system analog of the non-null assertion operator. Given a union type T, the type NonNullable<T> will be the same as T but without any union members of type null or undefined:
type Foo = string | number | undefined;
type NonNullableFoo = NonNullable<Foo>;
// type NonNullableFoo = string | number
In fact, the compiler actually uses it to represent the type of an expression that has the non-null assertion operator applied to it:
function nonNullAssertion<T>(x: T) {
const nonNullX = x!;
// const nonNullX: NonNullable<T>
}
So, everywhere you have a type T that includes null or undefined and you would like to remove it, you can use NonNullable<T>. In your code, you will need to do it multiple times. In the interest of something like brevity (of code, not my explanation), let's use a shorter alias:
type NN<T> = NonNullable<T>;
and then
class Client<C extends ClientData> extends EventEmitter {
exampleCache: Map<string, CustomExampleData<
NN<NN<NN<C["cacheStrategies"]>["example"]>["customCache"]>>
>;
}
This compiles without error, and behaves how I think you'd like it:
console.log(exampleObject.property1.toUpperCase()); // string
console.log(exampleObject.property2); // undefined
Playground link to code

How would I implement this type in Typescript (from Flow?) [duplicate]

Is there a way to extend the built-in Record (or a { [key:string]: string } interface) where you also define some fixed keys and their types?
Let's say we have this:
const otherValues = {
some: 'some',
other: 'other',
values: 'values',
}
const composedDictionary = {
id: 1,
...otherValues
}
I want to define an interface for composedDictionary where id is typed as number (and number only) and everything else as string.
I've tried this:
interface ExtendedRecord extends Record<string, string> {
id: number;
}
and this:
interface MyDictionary {
[key: string]: string;
id: number;
}
Both fail with:
Property 'id' of type 'number' is not assignable to string index type 'string'
Any ideas?
Ideally the index signature should reflect any possible indexing operation result. If you access composedDictionary with an uncheckable string key the result might be number if that string is actually 'id' (eg: composedDictionary['id' as string], the typings would say this is string but at runtime it turns out to be number). This is why the type system is fighting you on this, this is an inconsistent type.
You can define your index to be consistent will all properties:
interface MyDictionary {
[key: string]: string | number;
id: number;
}
There is a loophole to the checks typescript does for index and property consistency. That loop hole is intersection types:
type MyDictionary = Record<string, string> & {
id: number
}
const composedDictionary: MyDictionary = Object.assign({
id: 1,
}, {
...otherValues
});
The compiler will still fight you on assignment, and the only way to create such an inconsistent object in the type system is by using Object.assign
As the other answer said, TypeScript does not support a type in which some properties are exceptions to the index signature... and thus there is no way to represent your MyDictionary as a consistent concrete type. The inconsistent-intersection solution ({[k: string]: string]} & {id: number}) happens to work on property reads, but is difficult to use with property writes.
There was an old suggestion to allow "rest" index signatures where you can say that an index signature is supposed to represent all properties except for those specified.
There's also a more recent (but possibly shelved) pair of enhancements implementing negated types and arbitrary key types for index signatures, which would allow you to represent such exception/default index signature properties as something like { id: number; [k: string & not "id"]: string }. But that doesn't compile yet (TS3.5) and may never compile, so this is just a dream for now.
So you can't represent MyDictionary as a concrete type. You can, however, represent it as a generic constraint. Using it suddenly requires that all your previously concrete functions must become generic functions, and your previously concrete values must become outputs of generic functions. So it might be too much machinery than it's worth. Still, let's see how to do it:
type MyDictionary<T extends object> = { id: number } & {
[K in keyof T]: K extends "id" ? number : string
};
In this case, MyDictionary<T> takes a candidate type T and transforms it into a version which matches your desired MyDictionary type. Then we use the following helper function to check if something matches:
const asMyDictionary = <T extends MyDictionary<T>>(dict: T) => dict;
Notice the self-referencing constraint T extends MyDictionary<T>. So here's your use case, and how it works:
const otherValues = {
some: "some",
other: "other",
values: "values"
};
const composedDictionary = asMyDictionary({
id: 1,
...otherValues
}); // okay
That compiles with no errors, because the parameter to asMyDictionary() is a valid MyDictionary<T>. Now let's see some failures:
const invalidDictionary = asMyDictionary({
id: 1,
oops: 2 // error! number is not a string
})
const invalidDictionary2 = asMyDictionary({
some: "some" // error! property id is missing
})
const invalidDictionary3 = asMyDictionary({
id: "oops", // error! string is not a number
some: "some"
})
The compiler catches each of those mistakes and tells you where the problem is.
So, this is the closest I can get to what you want in as of TS3.5. Okay, hope that helps; good luck!
Link to code

Use imports inside interface definition

I'm having a strange problem with typescript interfaces. Because I'm using mongoose models I need to define one, but for some reason it's not recognising things that I have explicitly imported. This part works fine:
export interface ITrip extends mongoose.Document {
//
}
export var TripSchema = new mongoose.Schema({
//
});
export var Trip = mongoose.model<ITrip>('Trip', TripSchema);
Now, I'm defining another interface, that has an array of Trip. I need this for subdocuments.
import {Trip, ITrip} from '../trips/trip.model';
export interface IFeed extends mongoose.Document {
lastSnapshot: {
trips: [Trip]
}
}
The TS compiler gives this error: feed.ts(12,13): error TS2304: Cannot find name 'Trip'. (referring to trips: [Trip]). It doesn't say that the import failed or anything. I can even use trip inside the same file to create new objects var a = new Trip({}); without problem. Inside the interface it breaks.
Trip isn't a type, it's a variable, so you can do this:
let t = Trip;
let t2 = new Trip({});
But you can't do this:
let t: Trip;
You should change it to typeof Trip:
export interface IFeed extends mongoose.Document {
lastSnapshot: {
trips: [typeof Trip]
}
}
Also, if you want IFeed.lastSnapshot.trips to be an array, then it should be:
trips: typeof Trip[]
What you declared is a tuple of one item.
Edit
With an object the assignment is always the same (both js and ts):
let o = {
key: "value"
}
But when declaring types in typescript then you're not dealing with values:
interface A {
key: string;
}
let o: A = {
key: "value"
}
In the mongoose documentation they are using only javascript so all of their examples don't include the type declarations.

Categories