Indexing Object using Enum - javascript

I have the following structure;
import data from "../data.min.json";
export enum TileType {
tree = 'tree',
rock = 'rock'
}
interface MapTile {
walkable: boolean;
positions: number[][];
}
export type MapTiles = {
[key in TileType]: MapTile
}
export interface Level1 {
mapTiles: MapTiles;
}
export interface RootObject {
level_1: Level1;
}
export default data as RootObject;
This works fine. However, when I try to use it like so;
const entries = Object.entries(data.level_1.mapTiles);
entries.forEach(([tileType, data]) => {
})
The value of tileType is a string, rather than an Enum. How can I get the enum value instead?
data structure:
{
"level_1": {
"mapTiles": {
"tree": {
"walkable": false,
"positions": [
[ 0, 0 ], [ 0, 40 ], [ 0, 80 ]]
},
"rock": {
"walkable": false,
"positions": [
[2, 4], [5, 7]
]
},
}
}
}

This has to do with the typings for Object.entries in lib.esXXX.object.d.ts. In lib.es2017.object.d.ts for example, the typings look like this:
entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];
As you can see, the key is assumed to be a string. If you instead changed it like so, things would work as you expect:
entries<T, U extends keyof T>(o: T): [U, T[U]][];
Now you obviously wouldn't want to update the typings on a standard library - so you could instead create your own typed function like so:
const objectEntries = <T, U extends keyof T>(inputObject: T) => {
return Object.entries(inputObject) as [U, T[U]][]
}
Using this you should get the typings you're looking for, and
objectEntries(data.level_1.mapTiles).forEach(([
tileType, // correctly inferred as "tree" | "rock"
data
]) => {})

Related

typescript use preset value for property value in array object error?

I have this simple typescript code for react
type fruitCode = 'apple' | 'banana'
interface fruitList {
name: fruitCode
}
const [arr, setArr] = useState<fruitList[] | []>([])
useEffect(() => {
const arrList = [{
name: 'apple'
}, {
name: 'banana'
}];
//error?
setArr(arrList)
}, [])
demo
https://codesandbox.io/s/react-typescript-forked-gi6fw?file=/src/App.tsx:103-391
how can I restrict my property value is either 'apple' and 'banana'?
Typescript cannot infer the type for arrList so set it directly
const arrList: fruitList[] = [
// ...
]
I would also remove | [] from the state type definition as it is redundant; an empty array still satisfies the <fruitList[]> requirement.
const [arr, setArr] = useState<fruitList[]>([])
Finally, your interfaces and types should not be part of the component. Move them above App
type fruitCode = "apple" | "banana";
interface fruitList {
name: fruitCode;
}
export default function App() {
// ...
}
You'll notice there are no warnings about your useEffect dependencies after this.

Creating string literal union from a property in an array of objects

Is it possible to get a literal string union type of a static array of object's specific property?
For example, I want to expose IconName as a type of literal strings from which is inferred from an array of objects.
I've seen it has been done to convert a read-only array to a string literal union, but not through an array of objects.
I don't think it's possible since we require some runtime interpretative functionality to access the array's values. But here's the example I'm trying to work through this svelte example https://codesandbox.io/s/svelte-typescript-forked-ef75y:
<script lang="ts">
interface Icon {
name: string
color: string
}
const icons: readonly Icon[] = <const>[
{
name: "close",
color: "red"
},
{
name: "open",
color: "blue"
}
];
const tuple = <K extends string[]>(...arr: K) => arr;
const iconNames = tuple(...icons.map(i => i.name));
type IconName = typeof iconNames[number];
export let name: IconName;
let displayIcon = icons.find((e) => e.name === name);
</script>
<h1>{displayIcon.color}</h1>
I guess the code below will do what you try to do. However, I suggest writing your type definitions in a separate file with .ts extension.
<script lang="ts">
interface Icon {
name: string;
color: string;
}
const icons = {
close: {
name: "close",
color: "red",
},
open: {
name: "open",
color: "blue",
},
} as const;
type IconName = keyof typeof icons;
export let name: IconName;
let displayIcon = icons[name];
</script>
<h1>{displayIcon.color}</h1>

typescript: How to define type as any part of enum?

I'm trying to create a translation module using typescript.
I want to define the languages as an enum parameter to create-text function, as like:
export enum Language {
He = "he",
En = "en",
}
const { createI18n, createI18nText } = createTextFunctions(Language);
const firstExample = createI18nText({
he: {
firstText: "שלום",
sc: {
hello: "שלום שוב"
}
},
en: {
firstText: "hello",
sc: {
hello: "hello again"
}
}
})
export const i18n = createI18n({
welcome: firstExample,
})
But my problem is that because the languages are are passed as params to a typescript function and the function infers the types, typescript is not alarming anything. I can create text with non-existing language and it will pass it,like createI18nText({ ar:{ hi : "hi" }}).
My text functions are these:
export type Languages = { [key: string]: string };
export const createTextFunctions = (languages: LanguagesD) => {
type I18nText<T extends object> = {
[k in keyof typeof languages]: T;
}
const createI18n = <T extends { [key: string]: I18nText<any>; }>(i18n: T) => {
return i18n;
};
const createI18nText = <T extends object>(text: I18nText<T>) => {
return text;
}
return {
createI18n,
createI18nText
}
}
So the code is running and doing whatever it needs to do, but I'm losing type control.
I do prefer to have my enum values lower-cased, so its an issue too. If this is the solution so I'll take it, but if there is any way to pass an enum-param and to run by its values it would be great.
You can make createTextFunctions use a generic. You will be able to customise the keys as you want when you create the text function :
// *L* must be a union of strings.
const createTextFunctions = <L extends string>() => {
type I18nText<T extends object> = Record<L, T>;
type I18n = Record<string, I18nText<any>>;
const createI18n = <T extends I18n>(i18n: T): T => {
return i18n;
};
const createI18nText = <T extends object>(text: I18nText<T>): I18nText<T> => {
return text;
}
return {
createI18n,
createI18nText
}
}
Then specify the Language as a union of strings:
type Language = "en" | "he";
And create/use the text functions:
const { createI18n, createI18nText } = createTextFunctions<Language>();
const firstExample = createI18nText({
he: {
firstText: "שלום",
sc: {
hello: "שלום שוב"
}
},
// If the key *en* is missing, typescript will complain.
en: {
firstText: "hello",
sc: {
hello: "hello again"
}
},
// If we add the key *us*, typescript will complain.
})
export const i18n = createI18n({
welcome: firstExample,
})
IMO union types are more comfortable to use than enums.

How to copy properties from one object to another in typescript

I'm looking for a type safe way to copy properties between objects. I have the code working in javascript and I'm trying to convert it to typescript, however I'm not able to get it working. I'am getting Type '' is not assignable to type 'undefined'.(2322)
Can someone please help me understand if it is possible to do what I'm trying to do and what am I missing?
enum Kind { PRIMARY, SECONDARY };
enum Size { SMALL, MEDIUM };
type IconProps = { kind: Kind; size: Size; };
type ButtonProps = { id: string; text: string; onClick: () => void; };
type Props = ButtonProps & IconProps;
function iconComponent(props: Partial<IconProps>) {}
function buttonComponent(props: Partial<ButtonProps>) {}
const isIconProp = (key: string): key is keyof IconProps => /^(kind|size)$/.test(key);
const isButtonProp = (key: string): key is keyof ButtonProps => /^(id|text|onClick)$/.test(key);
function initComponents(props: Partial<Props>) {
// Default values
const icon: Partial<IconProps> = {
kind: Kind.PRIMARY
};
const button: Partial<ButtonProps> = {
id: 'button'
};
// How to iterate through keys and assign properties to respective objects?
Object.keys(props).forEach(key => {
// Why is this not working?
if (isIconProp(key)) {
icon[key] = props[key]
}
if (isButtonProp(key)) {
button[key] = props[key];
}
// This works
if (key === 'kind') {
icon.kind = props.kind;
}
if (key === 'onClick') {
button.onClick = props.onClick;
}
});
return {
root: buttonComponent(button),
child: [iconComponent(icon)]
};
}
I came up with the below approach which seems to be good enough for now. Please comment if there is a better way to do it.
const mapTo = <T, S extends T, K extends keyof T>(target: T, source: S, key: K): void => {
target[key] = source[key];
};
Object.keys(props).forEach(key => {
if (isIconProp(key)) {
mapTo(icon, props, key);
}
if (isButtonProp(key)) {
mapTo(button, props, key);
}
});

Angular Interface with Enum

is possible to have an interface with enum at angular ?
I have an interface like this:
export interface UserModel {
title: TitleEnum
}
An Enum:
export enum TitleEnum {
Admin = 0,
Alfa = 1,
Beta = 2
}
So I am trying to get {{user.title}}, keep receiving the numbers, am I missing something?
This is how enums work! If you are looking assign the keys to strings, then you can use a string enum.
export enum TitleEnum {
Admin = 'Admin',
Alfa = 'Alfa',
Beta = 'Beta'
}
What you can also do is to use a property
//in your component html
<select>
<option *ngFor="let key of keys" [value]="key" [label]="race[key]"></option>
</select>
export enum RaceCodeEnum {
Asian = 1,
Mexican = 2,
AfricanAmerican = 3,
White = 4,
}
export class RaceSelect {
race= RaceCodeEnum;
constructor() {
this.keys = Object.keys(this.race).filter(k => !isNaN(Number(k)));
}
}

Categories