How to implement polymorphism correctly with protobuf.js and reflection

Viewed 307

Suppose I have a class hierarchy implemented in c# with Protobuf.net. (There would also be a Rectangle class which implements Shape, but I've omitted this for brevity.)

[ProtoContract]
[ProtoInclude(1, typeof(Circle))]
public class Shape {
}

[ProtoContract]
public class Circle : Shape {

    [ProtoMember(1)]
    public int Radius {get;set;}
}

I want to represent this same class hierarchy in TypeScript using protobuf.js. However, I can't seem to figure out how to map back the 'oneof' field to the subclass.

This is the best I've been able to come up with:

// It's not possible to make this inherit from 'Shape'
class Circle extends Message<Circle> {
  @Field.d(1, "int32")
  radius: number;
}

class Shape extends Message<Shape> implements IShape {
  @Field.d(1, Circle)
  circle: Circle;

  @OneOf.d("circle") // ,"rectangle"
  which: string;

  // Implements the property on ICircle
  get radius(): number {
    return this.circle.radius;
  }
}

interface IShape {
}

interface ICircle extends IShape {
  radius: number;
}

function isCircle(shape: IShape): shape is ICircle {
  return (shape as any).which === "circle";
}

const shape = new Shape({
  circle: new Circle({ radius: 5 }),
  which: "circle"
});

const buffer = Shape.encode(shape).finish();
const decoded = Shape.decode(buffer);

if (isCircle(decoded)) {
  const itsACircle: ICircle = decoded;
  // Do something
}

This feels hacky to me, because:

  1. We have to expose all the possible sub-class fields as properties on the base Shape class (in this case the radius property of Circle).
  2. None of the classes actually implement ICircle, so if we fail to properly expose some properties, the compiler won't warn us.
  3. The type guard relies on a cast to any.

Can anyone suggest a better way of achieving this?

0 Answers
Related