Reputation: 2737
The flow-typed libdef for express defines locals
as an object with mixed
values.
Specifying the actual value types results in the errors below... how should these specific types be annotated?
// From libdef
type Locals = {
[name: string]: mixed;
}
// Populated data
const locals: Locals = {
version: 1.2,
resources: {
a: "abc",
b: "def"
}
};
// Annotate the actual type
const version: number = locals.version; // Error: mixed is incompatible with number
const resources: {[string]: string} = locals.resources; // Error: mixed is incompatible with object type
Upvotes: 2
Views: 2922
Reputation: 2737
It looks like the only way here is to add refinement logic.
if (typeof locals.version === "number") {
const version: number = locals.version;
}
if (locals.resources instanceof Object) {
const resources: {[string]: string} = locals.resources;
}
Note that you could also just cast the data itself:
const version: number = Number(locals.version);
Or define a fresh copy:
const resources: {[string]: string} = {...locals.resources};
Upvotes: 1
Reputation: 3478
One way is to refine the type of whatever you've recieved until it fits the shape you're looking for. Usually, I make a handful of basic refinement functions and use those to build up larger refinements.
(Try)
// From libdef
type Locals = {
[name: string]: mixed;
}
// Populated data
const locals: Locals = {
version: 1.2,
resources: {
a: "abc",
b: "def"
}
};
// The type you want
type MyLocals = {
version: number,
resources: {
// maybe this one is a map? idk
[string]: string
}
}
// Some basic refinement functions
const refineString = (x: mixed): string => {
if (typeof x === 'string') {
return x
}
throw new Error("Not a string")
}
const refineNumber = (x: mixed): number => {
if (typeof x === 'number') {
return x
}
throw new Error("Not a number")
}
const refineObj = (x: mixed): {[string]: mixed} => {
if (x instanceof Object) {
return x
}
throw new Error("Not an object")
}
// More type-specifc refinement functions
const refineResources = (x: mixed): $ElementType<MyLocals, 'resources'> => {
const anObj = refineObj(x)
return Object.keys(anObj)
.reduce((acc, k) => Object.assign(acc, { [k]: refineString(anObj[k]) }), {})
}
const refineMyLocals = (x: mixed): MyLocals => {
const {version, resources} = refineObj(x)
return {
version: refineNumber(version),
resources: refineResources(resources)
}
}
// Now use them to assert a type
const myLocals: MyLocals = refineMyLocals(locals)
const version: number = myLocals.version;
const resources: {[string]: string} = myLocals.resources;
Alternatively, if the libdef is in the flow-typed
folder, just go in there and change the libdef. It will make that type specific to your project, but it might be the most effective way to handle it assuming you don't need the the [name: string]: mixed
type somewhere else in your code.
Upvotes: 2