How to take a subset of an object using an interface? - oop

Suppose I have this class and interface
class User {
name: string;
age: number;
isAdmin: boolean;
}
interface IUser {
name: string;
age: number;
}
And then I get this json object from somewhere
const data = {
name: "John",
age: 25,
isAdmin: true
}
I want to subset data using IUser and remove the isAdmin property like this
let user = subset<IUser>(data);
// user is now { name: "John", age: 25 }
// can safely insert user in the db
My question is how do I implement that function in TypeScript?
function subset<T>(obj: object) {
// keep all properties of obj that are in T
// keep, all optional properties in T
// remove any properties out of T
}

There's no way to do that which is better than:
function subset(obj: IUser) {
return {
name: obj.name,
age: obj.age
}
}
The typescript interfaces don't exist at runtime (which is when subset is invoked) so you cannot use the IUser interface to know which properties are needed and which aren't.
You can use a class which does "survive" the compilation process but:
class IUser {
name: string;
age: number;
}
Compiles to:
var IUser = (function () {
function IUser() {
}
return IUser;
}());
As you can see, the properties aren't part of the compiled output, as the class members are only added to the instance and not to the class, so even a class won't help you.
You can use decorator and metadata (more on that here) but that sounds like an overkill for your scenario.
Another option for a more generic subset function is:
function subset<T>(obj: T, ...keys: (keyof T)[]) {
const result = {} as T;
keys.forEach(key => result[key] = obj[key]);
return result;
}
let user1 = subset(data, "name", "age");
let user2 = subset(data, "name", "ag"); // error: Argument of type '"ag"' is not assignable to parameter of type '"name" | "age" | "isAdmin"'

Related

TypeScript: Recovering the "object literal may only specify known properties" check when passing through a generic function

I realize the "object literal may only specify known properties" doesn't work in all cases. In particular, it looks like it doesn't work when I pass the object literal through an identity-like function (playground link)
declare function setUser(arg: {name: string}): void;
declare function identity<T>(v: T): T;
setUser({name: 'a', age: 12}); // Error, good!
setUser(identity({name: 'a', age: 12})); // No error, sad.
const u = {name: 'a', age: 12};
setUser(u); // No error, but I'm used to this case already.
Is there a way to write identity in a way that will get back the error?
In my actual codebase, I'm not using the identity function, but a slight variant (playground link):
declare function setUser(arg: {name: string}): void;
type NonNullable<T> = T extends null ? never : T;
export type NullableToOptional<T> = {
[K in keyof T]: null extends T[K] ? NonNullable<T[K]> | undefined : T[K];
};
export function toOptional<T>(x: T): NullableToOptional<T> {
return x as NullableToOptional<T>;
}
setUser({name: 'a', age: 12}); // Error, good!
setUser(toOptional({name: 'a', age: 12})); // No error, sad.
Here you have a naive implementation. You will find explanation in comments
type Base = { name: string }
// credits goes to https://github.com/microsoft/TypeScript/issues/40248
type IsNever<T> = [T] extends [never] ? true : false;
/**
* Obtain extra keys
*/
type ExtraKeys<T, U> = Exclude<keyof U, keyof T>
type Validator<Obj> = (
Obj extends Base
? (
Base extends Obj
? Obj : (
/**
* If there are no extra keys
*/
IsNever<ExtraKeys<Base, Obj>> extends true
/**
* Return source object
*/
? Obj
/**
* Otherwise return source object where all extra keys are [never]
* It helps TS compiler to highlight only invalid props
*/
: Base & Record<ExtraKeys<Base, Obj>, never>
)
)
: never
)
/**
* Validator - validates the argument
*/
declare function setUser<
Name extends string,
Obj extends { name: Name }
>(arg: Validator<Obj>): void;
type NonNullable<T> = T extends null ? never : T;
export type NullableToOptional<T> = {
[K in keyof T]: null extends T[K] ? NonNullable<T[K]> | undefined : T[K];
};
/**
* We need to infer each key and value pair in order to be alphabetic
* to validate return type
*/
export function toOptional<
Prop extends PropertyKey,
Value extends string | number,
Obj extends Record<Prop, Value>>(x: Obj) {
return x as NullableToOptional<Obj>;
}
setUser({ name: 'a', age: 2 }); // Error
setUser(toOptional({ name: 'a', age: 12 })); // Error
setUser({ name: 'a' }); // Error
setUser(toOptional({ name: 'a' })); // Error
Playground

Implementing properties declared in interfaces in Kotlin

I'm new to Kotlin, so I have this interface.
interface User {
var nickName : String
}
Now I want to create a class PrivateUser that implements this interface. I have also to implement the abstract member nickName.
Via constructor it's very simple
class PrivateUser(override var nickName: String) : User
However when I try to implement member inside the class Idea generates me this code
class Button: User {
override var nickName: String
get() = TODO("not implemented")
set(value) {}
}
It's confusing to me how to implement it further.
Properties must be initialized in Kotlin. When you declare the property in the constructor, it gets initialized with whatever you pass in. If you declare it in the body, you need to define it yourself, either with a default value, or parsed from other properties.
Some examples:
class Button : User {
override var nickname = "Fred"
}
class Button(val firstName: String, val lastName: String) : User {
override var nickname = "${firstname[0]}$lastname"
}
The code generated by IDEA is useful if you want a non-default getter and/or setter, or if you want a property without a backing field (it's getter and setter calculate on the fly when accessed).
More examples:
class Button : User {
override var nickname = "Fred"
get() = if (field.isEmpty()) "N/A" else field
set(value) {
// No Tommy
field = if (value == "Tommy") "" else value
}
}
class Button(val number: Int) : User {
var id = "$number"
private set
override var nickname: String
get() {
val parts = id.split('-')
return if (parts.size > 1) parts[0] else ""
}
set(value) {
field = if (value.isEmpty()) "$number" else "$value-$number"
}
}

Supplied parameters do not match any signature of call target in typeScript using with Angular 2

Object structure look like as below:
export class Recipe {
public name: string;
public description: string;
public imagePath: string;
constructorn(name: string, desc: string, imagePath: string) {
this.name = name;
this.description = desc;
this.imagePath = imagePath;
}
}
And my call statement:
export class RecipeListComponent implements OnInit {
recipes: Recipe[] = [
new Recipe('Test Recipe', 'This is simply a test',
'https://cdn.pixabay.com/photo/2016/06/15/19/09/food-
1459693_960_720.jpg')
];
}
Though I am passing all the parameter but still I am getting the error "Supplied parameters do not match any signature of call target"
You misspelled constructor and might want to use parameter properties.
export class Recipe {
constructor(public name: string, public desc: string, public imagePath: string) {
// Insert logic..
}
}
This should do the job.

Using Typescript object spread operator with this keyword

In my code I often have to copy data from json to instantiate class in constructor.
function append(dst, src) {
for (let key in src) {
if (src.hasOwnProperty(key) {
dst[key] = src[key];
}
}
};
export class DataClass {
id: number;
title: string;
content: string;
img: null | string;
author: string;
// no methods, just raw data from API
}
export class AdoptedClass1 extends DataClass {
// has same fields as DataClass
showcase: string;
constructor (data: DataClass) {
append(data, this);
// do some stuff
}
}
// similar code for AdoptedClass2
I'm wondering if I can replace append function call in constructor with object spread operator
For your need I'll prefer to use Object.assign(this, data) over your custom made append function. Nevertheless have a look at the documentation to understand the limitation of it.
Back to your main question: it is not possible to use the spread operator to do what you want. Many people are interested in that feature but it has been put on hold as you can see here.
To get closer of what you ask we can refactor your code a little:
export class DataClass {
id: number
title: string
content: string
img: null | string
author: string
constructor(data: DataClass) {
Object.assign(this, data)
}
}
export class AdoptedClass1 extends DataClass {
showcase: string
constructor (data: DataClass) {
super(data)
// do some stuff
}
}
By simply adding the constructor to the data class you will be allowed to use super(data) in children and IMHO the code will be a lot cleaner.
You can use object spread operator by replacing this line:
append(data,this)
with this line
data = {...data, ...this};

Typescript - Why should I rewrite all members to implement an interface?

I have an interface with some optional variables like:
interface A {
id: string;
name?: string;
email?: string;
...
}
What I want to do is that
class B implements A {
constructor(x: string, y: string, ...) {
this.id = x;
this.name = y;
...
}
getName(): string {
return this.name;
}
}
I don't want to rewrite all members that I will use and I need some members to stay optional. Each interface will be implemented with only one class, so if I rewrite all the members in class B than the interface A becomes useless.
You may ask "Why you need interface A anyway?". I need it because I am using it from some other project, and I have to extend and implement it with some functions.
Any solution or different idea about that implementation?
One option is to use Object.assign like so:
interface A {
id: string;
name?: string;
email?: string;
}
class B implements A {
id: string;
name: string;
email: string;
constructor(data: A) {
Object.assign(this, data);
}
getName(): string {
return this.name;
}
}
(code in playground)