TypeScript: json and interface - reactjs

I have an interface for an element:
export interface iElm {
name?: string;
appearance?: string;
atomic_mass?: number;
boil?: number;
category?: string;
density?: number;
discovered_by?: string;
melt?: number;
molar_heat?: number;
named_by?: string;
number?: number;
period?: number;
phase?: string;
source?: string;
spectral_img?: string;
summary?: string;
symbol?: string;
xpos?: number;
ypos?: number;
shells?: number[];
electron_configuration?: string;
electron_configuration_semantic?: string;
electron_affinity?: number;
electronegativity_pauling?: number;
ionization_energies?: number[];
cpk_hex?: string;
}
I got this by using a utility similar to the one in this question (json to ts): How to convert a json to a typescript interface?
The json that I'm using is an object of element object that are all a bit different but one looks like this:
"helium": {
"name": "Helium",
"appearance": "colorless gas, exhibiting a red-orange glow when placed in a high-voltage electric field",
"atomic_mass": 4.0026022,
"boil": 4.222,
"category": "noble gas",
"density": 0.1786,
"discovered_by": "Pierre Janssen",
"melt": 0.95,
"molar_heat": null,
"named_by": null,
"number": 2,
"period": 1,
"phase": "Gas",
"source": "https://en.wikipedia.org/wiki/Helium",
"spectral_img": "https://en.wikipedia.org/wiki/File:Helium_spectrum.jpg",
"summary": "Helium is a chemical element with symbol He and atomic number 2. It is a colorless, odorless, tasteless, non-toxic, inert, monatomic gas that heads the noble gas group in the periodic table. Its boiling and melting points are the lowest among all the elements.",
"symbol": "He",
"xpos": 18,
"ypos": 1,
"shells": [2],
"electron_configuration": "1s2",
"electron_configuration_semantic": "1s2",
"electron_affinity": -48,
"electronegativity_pauling": null,
"ionization_energies": [2372.3, 5250.5],
"cpk_hex": "d9ffff"
}
As you can see everything lines up with the iElm interface (properties are optional because there are some weird corner case elements)
Now here's my problem: I have a React function component that takes in an iElm:
const Element: FC<iElm> = (props) => {
// some jsx stuff with the data
}
I can pass properties of the json to Element like so:
<Element name={table.boron.name}/>
But is there some workaround so that I don't have to go through every property one by one and copy it over?

You can simply use the spread operator to pass the entire object in:
<Element {...table.boron} />

Related

Typescript: How to convert an array of keys into a union type of objects?

1. Array of Strings -> Union of Strings (It works)
I saw a solution creates a union of string literals from an array of strings.
const keys = ["cat", "dog"] as const;
type Keys = typeof keys[number]; // "name" | "age"
2. Array of Strings -> Union of Objects (Is it possible?)
My question is if it's possible to create a union of objects from an array of strings? The code below works, I just want to shorten the SET_PROPERTY_PAYLOAD with something like keyof IObject and generate a union of object types from there.
interface IObject = {
id: string;
name: string;
age: number;
}
// Is it possible to shorten this type using typescript's primitives
type SET_PROPERTY_PAYLOAD =
| {
id: string;
propertyName: "id"; // keyof IObject at index 0
value: string; // IObject["id"]
}
| {
id: string;
propertyName: "name"; // keyof IObject at index 1
value: string; // IObject["name"]
}
| {
id: string;
propertyName: "age"; // keyof IObject at index 2
value: number; // IObject["age"]
};
My goal for this is to narrow the type of value by inferring from the current value of propertyName.
You want a mapped type.
interface IObject {
id: string;
name: string;
age: number;
}
type SET_PROPERTY_PAYLOAD = {
[K in keyof IObject]: {
id: string,
propertyName: K,
value: IObject[K]
}
}[keyof IObject]
This type maps over the keys in IObject and create a new object type for each key.
SET_PROPERTY_PAYLOAD should then be exactly equivalent to your long form version above.
See Playground

conditional types for nav item

I would like to define my interface so that when you give a nav that is a navigationItem, you can optionally give childs for a dropdown. when the childs property is given I would like to enforce the icon property of the navigationItem.
When no childs property are given the icon property should not be given.
Any idea on how I could achieve that ?
this is my current interface
interface dropdown {
name: string;
href: string;
icon: IconDefinition;
}
interface navigationItem {
name: string;
href: string;
icon: IconDefinition; // only needed if the childs is given
childs?: dropdown[];
}
You should be able to do this using two different Interfaces like this.
Have one with
interface Type1 {
name: string;
href: string;
childs: never;
}
The second one will be
interface Type2 {
name: string;
href: string;
childs: dropdown[];
icon: IconDefinition;
}
Then you can use both the types using a conditional like this
value: Type1 | Type2
Why this works is, when you add icon: never to Type1, you can only have name and href keys.
So if someone adds the childs key, then the type becomes Type2 which needs to have icon given as well.
So, if const a: Type1 | Type2 = { name: '', href: '', childs: [] };, it throws
Type '{ name: string; href: string; childs: []; }' is not assignable to type 'Type1 | Type2'.
Property 'icon' is missing in type '{ name: string; href: string; childs: dropdown[]; }' but required in type 'Type2'.
If you want to use a single type instead, define a new type like this.
type Type3 = Type1 | Type2;
Then use it like this
const a: Type3;

How do I specify an array of objects in Typescript using interface with specific attribute naming

I'm new to Typescript thought I got a hang of it but ran into an issue today of creating an interface for a complex dynamic object from JSON, converted it to generic names for the attributes below.
I'm getting error messages "Source has X element(s) but target allows only 1.". How can I extend my arrays to accept multiple objects, but still retain the attribute names? I have seen indexer solutions but it seems to loose the value of getting an expected JSON object overview.
interface Props {
arrayName: [
{
attributeName: string;
attributeArray: [
{
attributeToken: string;
attributeArray: [
{
attributeName: number;
attributeName: number;
comment: string;
},
];
},
];
attributeArray: [
{
attrbuteName: string;
attributeValue?: number;
attributeLevel: number | null;
attributeMeasurement: number | null;
attributeUnit?: string;
},
];
attributeBoolean: false;
attributeValue: 199.0;
}
];
}
The type [SomeType] is a tuple with one element. To declare an array with an unknown number of items use SomeType[]
The tuple form lets you make a type like [number, string, boolean] which requires a number at index zero, a string at index 1, and a boolean at index 2. There must be exactly 3 items in the array, and the type of each index is known at compile time.
So you don't want a tuple here. Fixing it is simple. Instead of:
attributeArray: [
{
attributeName: number;
attributeName: number;
comment: string;
},
];
You want:
attributeArray: {
attributeName: number;
attributeName: number;
comment: string;
}[],

Typescript array confusion - TS 2488 with Type 'never'

They is the following code errors on the for loop with TS 2488 for the type of apps - which at the time is of type never?
If I remove the 3rd comparison in the if, Array.isArray(apps), then the the type is correct in the for loop (ILuisApp[]) instead of type never.
export interface ILuisFull {
id: string;
name: string;
description: string;
culture: string;
versionsCount: number;
app_createdDateTime: string;
endpointHitsCount: number;
activeVersion: string;
tokenizerVersion: string;
}
export interface ILuisApp {
id: string;
name: string;
description: string;
culture: string;
usageScenario: string;
domain: string;
versionsCount: number;
createdDateTime: string;
endpointHitsCount: number;
activeVersion: string;
ownerEmail?: string;
tokenizerVersion: string;
}
export const transform = (apps: ILuisApp[]): ILuisFull[] => {
if (!apps || apps.length === 0 || Array.isArray(apps)) return [] as ILuisFull[];
let fullTable: Array<ILuisFull> = [];
// apps is type never unless remove 3rd comparison in if above
for (var val of apps) {
console.log(val);
}
return fullTable;
}
ts config includes:
"target": "es5",
"module": "commonjs",
"lib": [
"es2016",
"es2017.object",
"esnext.asynciterable"
],
It looks like compiler thinks that apps is not initialized. It thinks that arr is null. So it says:
Type 'never' must have a 'Symbol.iterator' method that returns an
iterator.
However, it is possible to use casting apps as ILuisApp[]) to say what type of apps is:
for (let val of apps as ILuisApp[]) {
console.log(val);
}

Editing child array objects in Angular 2

I'm new to Angular and have hit a wall in my project which i'm hoping some of you more experienced devs can help me smash through!
I have a JSON file which is called by a service. This data is then injected into the component via my interface where I am using a custom Dictionary class for the 'lists' (list1, list2 etc) i'm looking to let the user edit.
Everything was all good until I had to start trying to use the 'nested' lists from my JSON file. I cannot find a way to use this in my component so that the user can edit the strings.
My JSON looks like this:
[
{
"id": 1,
"insurer": "My Insurer",
"product": "Healthier Solutions",
"heading1": "Insurance",
"heading2": "Insurance Product Information Document",
"heading3": "What is the type of insurance?",
"heading4": "What is insured?",
"heading5": "What is NOT insured?",
"heading6": "Are there restrictions on my cover?",
"heading7": "Where am I covered?",
"heading8": "What are my obligations?",
"heading9": "When and how do I pay?",
"heading10": "When does my cover start and end?",
"heading11": "How do I cancel my contract?",
"subHeading1": "Insurance Company",
"subHeading2": "Healthier Solutions",
"textBox1": "Private Medical Insurance",
"textBox2": "You will have access to Aviva’s Key Hospital list in the UK.",
"textBox3": "Monthly by direct debit. The first payment will be collected within 14 days of setting up the policy. The following payments will be collected monthly thereafter.",
"textBox4": "The policy will start on your chosen start date and will end exactly 12 months later.",
"textBox5": "Please contact...",
"list1": {
"item1": "In and day patient hospital charges",
"item2": "In and day patient diagnostic tests",
"item3": "In and day patient diagnostic scans",
"item4": "In and day patient specialist fees",
"item5": "In and day patient radiotherapy",
"item6": "In and day patient chemotherapy",
"item7": "Outpatient specialist consultations",
"item8": "Outpatient diagnostic tests",
"item9": "Outpatient diagnostic scans",
"item10": "Outpatient radiotherapy",
"item11": "Outpatient chemotherapy"
},
"list2": {
"item1": "Routine monitoring of conditions",
"item2": "Pre-existing conditions",
"item3": "GP consultations and treatment",
"item4": "Emergency treatment",
"item5": "Chronic conditions (e.g. the management of incurable conditions such as asthma, diabetes, arthritis)",
"item6": "Self-inflicted conditions"
},
"list3": {
"item1": "Outpatient psychiatric treatment limited to £2,000 a year",
"item2": "Fees and charges by specialists are limited to Aviva’s fee guidelines"
},
"list4": {
"item1": "You must be a UK resident.",
"item2": "You must pay your monthly premiums in full and on time.",
"item3": "You must be registered with a UK GP at the point of making a claim.",
"item4": "You must ensure that all information that you provide is accurate and true to the best of your knowledge and belief."
}
}
]
My interface looks like this:
import {
IDictionary
} from "../models/IDictionary";
export interface IProducts {
id: number;
insurer: string;
product: string;
heading1: string;
heading2: string;
heading3: string;
heading4: string;
heading5: string;
heading6: string;
heading7: string;
heading8: string;
heading9: string;
heading10: string;
heading11: string;
subHeading1: string;
subHeading2: string;
textBox1: string;
textBox2: string;
textBox3: string;
textBox4: string;
textBox5: string;
list1: IDictionary;
list2: IDictionary;
list3: IDictionary;
list4: IDictionary;
}
Here is my custom dictionary class:
export interface IDictionary {
add(key: string, value: any): void;
remove(key: string): void;
containsKey(key: string): boolean;
keys(): string[];
values(): any[];
item1: string;
item2: string;
item3: string;
item4: string;
item5: string;
item6: string;
item7: string;
item8: string;
item9: string;
item10: string;
item11: string;
}
Here is what my component uses to enable the user to edit the data via a HTML input by importing my interface and using my 'edit' model:
editProduct(k:any) {
this.edit.heading1 = this.products[k].heading1;
this.edit.heading2 = this.products[k].heading2;
this.edit.heading3 = this.products[k].heading3;
this.edit.heading4 = this.products[k].heading4;
this.edit.heading5 = this.products[k].heading5;
this.edit.heading6 = this.products[k].heading6;
this.edit.heading7 = this.products[k].heading7;
this.edit.heading8 = this.products[k].heading8;
this.edit.heading9 = this.products[k].heading9;
this.edit.heading10 = this.products[k].heading10;
this.edit.heading11 = this.products[k].heading11;
this.edit.subHeading1 = this.products[k].subHeading1;
this.edit.subHeading2 = this.products[k].subHeading2;
this.edit.textBox1 = this.products[k].textBox1;
this.edit.textBox2 = this.products[k].textBox2;
this.edit.textBox3 = this.products[k].textBox3;
this.edit.textBox4 = this.products[k].textBox4;
this.edit.textBox5 = this.products[k].textBox5;
this.edit.list1item1 = this.products[k].list1.item1;
this.myValue = k;
}
updateProduct() {
let k = this.myValue;
for (let i = 0; i < this.products.length; i++) {
if (i === k) {
this.products[i] = this.edit;
this.edit = {};
}
}
this.showSuccessAlertMessage = "This product has been successfully updated.";
}
I'm looking to do something like the above - 'this.edit.list1item1 = this.products[k].list1.item1;', but that is not working.
Really hope that makes sense please do let me know if it doesn't and i'll do my best to explain it clearer.
Thanks.

Resources