1
votes

Say I am returned some random object data. Now I wish validate and cast it to the appropiate type for use in the rest of my application. To do this I wrote the following utilty function.

static convertSingleSafe<T: (number | boolean | string | void)>(
        data: {[string]: mixed},
        name: string,
        type: 'number' | 'boolean' | 'string' | 'object' | 'undefined'): T {
    if (typeof data[name] === type ) {
        return data[name]
    } else  {
        throw new KeyError(`Badly formated value ${name}`, '')
    }
}

Which would be called like:

const data: {[string]: mixed} = {val: 10, otherdata: 'test'};
//data would come from IE JSON.parse() of a request to server.
const val: number = convertSingleSafe<number>(data, 'val', 'number');
const msg: string = convertSingleSafe<string>(data, 'otherdata', 'string);

The problem, however, with this is that flow doesn't seem to understand the assertions in the convertSingleSafe function. On return data[name] the following error is shown:

Error:(82, 20) Cannot return data[name] because: Either mixed [1] is incompatible with number [2]. Or mixed [1] is incompatible with boolean [3]. Or mixed [1] is incompatible with string [4].

Even though I quite explicitly test the value for that specific.


The other option, to let the generic be part of the data type gives the following error:
static convertSingleSafe<T: (number | boolean | string | void)>(
        data: {[string]: T},
        name: string,
        type: 'number' | 'boolean' | 'string' | 'object' | 'undefined'): T

Error:(191, 41) Cannot call FlowTest.convertSingleSafe with d bound to data because in the indexer property: Either mixed [1] is incompatible with number [2]. Or mixed [1] is incompatible with boolean [3]. Or mixed [1] is incompatible with string [4].

So (how) can I do this, without going through any casts?

1

1 Answers

0
votes

The thing you've gotta understand about type refinement in flow is that it's not very smart. Flow is basically looking for a construction like typeof <something> === 'number', like imagine it's using regular expressions to scan your code (this is not actually the case). Here's an example that does what I think you want:

static convertSingleSafe(
  data: {[string]: mixed},
  name: string,
  type: 'number' | 'boolean' | 'string' | 'object' | 'undefined'
): number | boolean | string | {} | void {
  const subject = data[name];
  if (type === 'number' && typeof subject === 'number') {
    return subject;
  }
  if (type === 'boolean' && typeof subject === 'boolean') {
    return subject;
  }
  if (type === 'string' && typeof subject === 'string') {
    return subject;
  }
  if (type === 'undefined' && typeof subject === 'undefined') {
    return subject;
  }
  if (type === 'object' && subject && typeof subject === 'object') {
    return subject;
  }
  throw new KeyError(`Badly formated value ${name}`, '');
}

And here's the generic version.