james emanon
james emanon

Reputation: 11807

FlowType errors using Object.entries

So, I have the following code, but flow errors keep popping up. I've tried to cast the Object.entries, but just won't work - others things to. Any insight?

type Fields = {
  name: string,
  func: (*) => boolean
};

type S = {
  key1: Fields,
  bill: Fields
}

var a: S = {
  key1: {name: 'mary', func: (str) => str === 'mary'},
  bill: {name: 'bill', func: (str) => str === 'bill'}
}

var c = Object
  .entries(a)
  .map(([key, obj]) => obj.func(key) ? obj : false)
  .filter(f => f)
  .reduce((acc, c) => {
    return 'something here'
   }, {});

I've left some things off, but the slow is the same. Flow is reading that entries as a return Tuple Type. I've tried all sorts of things, but instead of mudding things up, I left it untouched.

I can't seem to annotate the destructured items here ([key, obj]), get tuple errors...

Any assistance on getting that code assigned to var c, to work with annotations etc..?

The errors I get: Cannot call method on mixed type (from obj.func) Cannot assign value in Tuple etc..

Upvotes: 4

Views: 3986

Answers (3)

claassja
claassja

Reputation: 61

Replacing Object.entries with Object.keys + lookup fixes flow errors for me assuming the input object is properly typed.

i.e. replace Object.entries(a) with Object.keys(a).map(key => [key, a[key]])

This works with flow:

type Fields = {
  name: string,
  func: (*) => boolean
};

type S = {
  key1: Fields,
  bill: Fields
}

var a: S = {
  key1: {name: 'mary', func: (str) => str === 'mary'},
  bill: {name: 'bill', func: (str) => str === 'bill'}
}

var c = Object
  .keys(a)
  .map(key => a[key].func(key) ? obj : false)
  .filter(f => f)
  .reduce((acc, c) => {
    return 'something here'
   }, {});

Upvotes: 2

Engineer
Engineer

Reputation: 8847

In my case, I had:

    let objectsByName : { [string] : MyObjectType } = {}; //simple map
    ...
    objectsByName[object.name] = object; //call repeatedly to populate map.
    ...
    let results : any[] = []; //next we will populate this

Trying to operate on it like this failed for Flow (though this is executable JavaScript):

    for (let [name : string, object : MyObjectType] of Object.entries(objectsByName))
    {
        let result = doSomethingWith(object); //<- error on arg
        results.push(result);
    }

This succeeded for Flow:

    for (let name : string in objectsByName)
    {
        let object = objectsByName[name];
        let result = doSomethingWith(object); //<- error on arg
        results.push(result);
    }

It is annoying having to change code structure to suit a supposedly non-intrusive system like Flow comment types, which I chose in the hopes of making my code completely oblivious to Flow's presence. In this case I have to make an exception and structure my code as Flow wants it.

Upvotes: 2

loganfsmyth
loganfsmyth

Reputation: 161457

The error is accurate. Object.entries has the type

entries(object: any): Array<[string, mixed]>;

It has no way to know what the type of the second item in the tuple will be. That means your code

.map(([key, obj]) => obj.func(key) ? obj : false)

would need to do

.map(([key, obj]) => {
    if (typeof obj.func !== 'function') throw new Error();
    return obj.func(key) ? obj : false;
})

so that flow knows that it is guaranteed to be a function.

Alternatively, you could change your data structure to use a type where the second item in the tuple has a guaranteed type, like Map, e.g.

type Fields = {
  name: string,
  func: (string) => boolean
};

type S = Map<string, Fields>;

var a: S = new Map([
  ['key1', {name: 'mary', func: (str) => str === 'mary'}],
  ['bill', {name: 'bill', func: (str) => str === 'bill'}],
]);

var c = Array.from(a, ([key, obj]) => obj.func(key) ? obj : false)
  .filter(f => f)
  .reduce((acc, c) => {
    return 'something here'
   }, {});

Upvotes: 7

Related Questions