2

Closed

Can I define an Interface that can hold an object or an array?

description

I am trying to define an interface that can hold an object or an array. For example, a tree where internal nodes are arrays and leaf nodes are objects could have a traversal function that takes a single argument which is an array or an object. To walk the tree the traversal function might test the argument and if an object (no length property) then process the leaf else call traverse on each element of the array.

I expected (hoped?) this would work:
interface ILeafOrNode {
    [node?: number] : ILeafOrNode;
    leaf?: string;
}

function Foo() {
    var root0: ILeafOrNode = { leaf: "leaf" };
    var root1: ILeafOrNode = [{ leaf: "left leaf", leaf: "right leaf" }];
}
I actually got warnings for both lines in Foo:

Error 1 Cannot convert '{ leaf: string; }' to 'ILeafOrNode': Index signatures of types '{ leaf: string; }' and 'ILeafOrNode' are incompatible: Call signature expects 0 or fewer parameters

Error 2 Cannot convert '{ leaf: string; }[]' to 'ILeafOrNode'

I also don't grok the messages in the error. So if that behavior is expected please help me to understand what the error message is telling me about the TypeScript type system.

Thanks!
Chris
Closed Jul 28 at 10:18 PM by jonturner
As part of our move to GitHub, we're closing our CodePlex suggestions and asking that people move them to the GitHub issue tracker for further discussion. Some feature requests may already be active on GitHub, so please make sure to look for an existing issue before filing a new one.

You can find our GitHub issue tracker here:
https://github.com/microsoft/typeScript/issues

comments

middlewest wrote Apr 23, 2013 at 3:27 AM

I would use the visitor pattern for a situation like that.
export interface ILeafOfNode {
   void accept(visitor: ILeafOfNodeVisitor);
}
export interface INode extends ILeafOrNode {
   [node?: number] : ILeafOrNode;
}
export interface ILeaf extends ILeafOrNode {
   leaf?: string;
}
export interface ILeafOfNodeVisitor {
   visitLeaf(leaf: ILeaf);
   visitNode(node: INode);
}
export class Traverser {
   traverseWhatever(something: ILeafOrNode) {
      var visitor = new TraverseVisitor(this);
      something.accept(visitor);
   }
   traverseLeaf(leaf: ILeaf) {
      // put your code here
   }
   traverseNode(node: INode) {
      // put your code here
   }
}

export class TraverseVisitor implements ILeafOrNodeVisitor {
   constructor(private traverser: Traverser) {}
   visitLeaf(leaf: ILeaf) {
      this.traverserTraverseLeaf(leaf);
   }
   visitNode(node: INode) {
      this.traverserTraverseNode(node);
   }
}

cretz wrote Apr 23, 2013 at 3:22 PM

This is loosely related to https://typescript.codeplex.com/workitem/577 which is about defining index signature overloads.

jonturner wrote Apr 30, 2013 at 6:42 PM

In 0.9.0, you should be able to make the index non-optional and get the effect you're looking for. We've tweaked how indexers are handled to be more consistent.
interface ILeafOrNode {
    [node: number] : ILeafOrNode;
    leaf?: string;
}

function Foo() {
    var root0: ILeafOrNode = { leaf: "leaf" };
    var root1: ILeafOrNode = [{ leaf: "left leaf", leaf: "right leaf" }];
}