In typescript 3.0.3, I import a json file like this:
import postalCodes from '../PostalCodes.json';
It has this format:
{
"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
I want to use it like this:
// first.postalCode is of type string
const x = postalCodes[first.postalCode];
I get the error: "Element implicitly has an 'any' type because type '...very long type signature...' has no index signature."
Is there a way to get this working with the automatically generated json types, so that I can look up a postal code by it's string key dynamically?
My best approach right now is to have an intermediary ts file like:
import postalCodes from './PostalCodes.json';
export const PostalCodesLookup = postalCodes as {
[key: string]: { code: number, city: string, isPoBox: boolean }
};
Since TypeScript v2.9 you can enable resolveJsonModule
flag in compilerOprions
of your tsconfig.json file like so:
{
"compilerOptions": {
// ... other options
"resolveJsonModule": true
},
}
Now TypeScript should automatically resolve the types in your imported json file.
To work around the index type issue, I could suggest two options:
Enable suppressImplicitAnyIndexErrors
in your tsconfig.json. This will suppress this error message. You won't get any type hints either.
Create some types for the JSON, and use those instead of just 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"'.
];
Depending on how "hard-coded" the usage will be, exporting the PostalCode
type could work out well for you.
You could also write a function that checks against the JSON at run-time:
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
}
I think your main issue here is not that the compiler needs to infer the type of postalCodes
in some different way, but that first.postalCode
is not known to be one of the keys of postalCodes
. Since first.postalCode
is of type string
, it is reasonable for the compiler to warn you about this.
So you need to do some kind of type guard to convince the compiler to narrow first.postalCode
from string
to keyof typeof postalCodes
. I don't think any of the builtin control-flow type guards will do this sort of narrowing for you (first.postalCode in postalCodes
does act as a type guard in some cases but only to narrow the type of postalCodes
, which is not what you want.) Luckily you can implement a user-defined type guard to give you the behavior you're looking for:
function isKeyof<T extends object>(obj: T, possibleKey: keyof any): possibleKey is keyof T {
return possibleKey in obj;
}
You can then use it as follows:
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
}
Note that you do have to handle the case where first.postalCode
is not one of the keys of postalCodes
, which is something you really should do, if all you know about first.postalCode
is that it's a string
.
Caveat: isKeyOf(obj, key)
is not fully type safe in general. It's possible in TypeScript for a value obj
to have more properties than the compiler knows about in keyof typeof obj
. That is, types are not exact. In the most extreme example, if obj
is declared as type {}
, then keyof typeof obj
is never
, despite the fact that obj
may well have properties. This is why the common request to have Object.keys(obj)
return Array<keyof typeof obj
> is always rejected.
Luckily for us, this caveat is not an issue for object literals with inferred types like postalCodes
. That's because you know for sure that typeof postalCodes
is exact; there are no extra properties to worry about. in general it's not safe to narrow key
to keyof typeof obj
, as shown by
Hope that helps; good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With