I Typescript, hvordan importere json og dynamisk slå opp etter nøkkel?
I TypeScript 3.0.3 importerer jeg en JSON-fil slik:
import postalCodes from '../PostalCodes.json';
Den har dette formatet:
{
"555": { "code": 555, "city": "Scanning", "isPoBox": true },
"800": { "code": 800, "city": "Høje Taastrup", "isPoBox": true },
"877": { "code": 877, "city": "København C", "isPoBox": true },
"892": { "code": 892, "city": "Sjælland USF P", "isPoBox": true },
"893": { "code": 893, "city": "Sjælland USF B", "isPoBox": true },
"897": { "code": 897, "city": "eBrevsprækken", "isPoBox": true },
"899": { "code": 899, "city": "Kommuneservice", "isPoBox": true },
"900": { "code": 900, "city": "København C", "isPoBox": true },
"910": { "code": 910, "city": "København C", "isPoBox": true },
"917": { "code": 917, "city": "Københavns Pakkecenter", "isPoBox": true },
... and so on
Jeg vil bruke det slik:
// first.postalCode is of type string
const x = postalCodes[first.postalCode];
Jeg får feilen: "Elementet har implisitt typen 'any' fordi typen '...veldig lang type signatur...' ikke har noen indeks-signatur."
Finnes det en måte å få dette til å fungere med de automatisk genererte json-typene, slik at jeg kan slå opp et postnummer ved hjelp av dets strengnøkkel dynamisk?
Min beste tilnærming akkurat nå er å ha en mellomliggende ts-fil som:
import postalCodes from './PostalCodes.json';
export const PostalCodesLookup = postalCodes as {
[key: string]: { code: number, city: string, isPoBox: boolean }
};
Svar #1
Siden TypeScript v2.9 kan du aktivere resolveJsonModule-flagget i compilerOptions i din tsconfig.json-fil slik:
{
"compilerOptions": {
// ... other options
"resolveJsonModule": true
},
}
Nå bør TypeScript automatisk løse typene i din importerte JSON-fil.
For å omgå problemet med indekstype, kan jeg foreslå to alternativer:
Aktiver
suppressImplicitAnyIndexErrorsi din tsconfig.json. Dette vil undertrykke denne feilmeldingen. Du vil heller ikke få noen typeindikasjoner.Lag noen typer for JSON, og bruk dem i stedet for bare
string:import codes from '../codes.json'; type PostalCode = keyof typeof codes; const goodSring: string = '555'; const badString: string = '2'; const goodCode: PostalCode = '555'; const badCode: PostalCode = '2'; // Error:(39, 7) TS2322: Type '"2"' is not assignable to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"'. const array: [] = []; const obj = {some: 'prop'}; const num: number = 123; const list: PostalCode[] = [ '555', '2', // Error:(43, 5) TS2322: Type '"2"' is not assignable to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"'. goodCode, badCode, goodSring, // Error:(46, 5) TS2322: Type 'string' is not assignable to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"'. badString, // Error:(47, 5) TS2322: Type 'string' is not assignable to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"'. goodSring as PostalCode, badString as PostalCode, // no protection here array as PostalCode, // Error:(54, 13) TS2352: Conversion of type '[]' to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '[]' is not comparable to type '"917"'. num as PostalCode, // Error:(55, 13) TS2352: Conversion of type 'number' to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. obj as PostalCode, // Error:(56, 13) TS2352: Conversion of type '{ some: string; }' to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '{ some: string; }' is not comparable to type '"917"'. ];Avhengig av hvor «hardkodet» bruken vil være, kan det å eksportere
PostalCode-typen fungere bra for deg.Du kan også skrive en funksjon som sjekker mot JSON på kjøretid:
import codes from '../codes.json'; export type PostalCode = keyof typeof codes; function verifyCode(s: string): s is PostalCode { return codes[s as PostalCode] !== undefined; // or use Object.keys, or some other method } let city: string; const str: string = 'asd'; if (verifyCode(str)) { city = codes[str].city; // in this branch `str` is `PostalCode` } else { city = codes[str].city; // complains about the index signature }
Svar #2
Jeg tror hovedproblemet ditt her ikke er at kompilatoren trenger å utlede typen til postalCodes på en annen måte, men at first.postalCode ikke er kjent for å være en av nøklene i postalCodes. Siden first.postalCode er av typen string, er det rimelig at kompilatoren advarer deg om dette.
Så du trenger å gjøre en slags typebeskyttelse for å overbevise kompilatoren om å avgrense first.postalCode fra string til keyof typeof postalCodes. Jeg tror ikke noen av de innebygde kontrollflyt-typebeskyttelsene vil gjøre denne typen innsnevring for deg (first.postalCode in postalCodes fungerer som en typebeskyttelse i noen tilfeller, men bare for å avgrense typen til postalCodes, noe som ikke er det du ønsker.) Heldigvis kan du implementere en brukerdefinert typebeskyttelse for å få den oppførselen du ser etter:
function isKeyof<T extends object>(obj: T, possibleKey: keyof any): possibleKey is keyof T {
return possibleKey in obj;
}
Du kan deretter bruke det som følger:
declare const first: {postalCode: string};
if (isKeyof(postalCodes, first.postalCode)) {
const x = postalCodes[first.postalCode]; // no error
} else {
// uh oh, first.postalCode is not valid
}
Merk at du må håndtere tilfellet der first.postalCode ikke er en av nøklene i postalCodes, noe du virkelig bør gjøre, hvis alt du vet om first.postalCode er at det er en string.
Advarsel: isKeyOf(obj, key) er generelt ikke fullstendig typesikker. Det er mulig i TypeScript for en verdi obj å ha flere egenskaper enn kompilatoren vet om i keyof typeof obj. Det vil si at typer ikke er eksakte. I det mest ekstreme eksemplet, hvis obj er deklarert som typen {}, så er keyof typeof obj never, til tross for at obj godt kan ha egenskaper. Dette er grunnen til at den vanlige forespørselen om å få Object.keys(obj) til å returnere Array<keyof typeof obj> alltid blir avvist.
Heldigvis for oss er denne forbeholden ikke et problem for objektliteraler med utledede typer som postalCodes. Det er fordi du vet sikkert at typeof postalCodes er eksakt; det finnes ingen ekstra egenskaper å bekymre seg for. Generelt er det ikke trygt å innsnevre key til keyof typeof obj, som vist av
Håper det hjelper; lykke til!
