bufbuild / protobuf-es

Protocol Buffers for ECMAScript. The only JavaScript Protobuf library that is fully-compliant with Protobuf conformance tests.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Oneof is not constructable in TypeScript

StarpTech opened this issue · comments

Describe the bug

Try to create a response message like this.

message AnalyticsViewRow {
  map<string, Value> value = 1;
}

message GetAnalyticsViewResponse {
  Response response = 1;
  AnalyticsViewResult view = 2;
}

message Value {
  // The kind of value.
  oneof kind {
    // Represents a double value.
    double number_value = 1;
    // Represents a string value.
    string string_value = 2;
    // Represents a boolean value.
    bool bool_value = 3;
  }
}

If you want to return this as a response in Node.js it only suggests kind as input but at the same time it complains that value and case is missing. I couldn't find a way to create a valid GetAnalyticsViewResponse response payload without type issues.

const rows: PlainMessage<AnalyticsViewRow>[] = result.map((row) => {
      return {
        value: {
          myField: {
            kind: ".....",
          }
        },
      };
    });
 Type '{ value: { myField: { kind: string; }; }; }[]' is not assignable to type 'PlainMessage<AnalyticsViewRow>[]'.
  Type '{ value: { myField: { kind: string; }; }; }' is not assignable to type 'PlainMessage<AnalyticsViewRow>'.
    Types of property 'value' are incompatible.
      Type '{ myField: { kind: string; }; }' is not assignable to type '{ [key: string]: PlainMessage<Value>; [key: number]: PlainMessage<Value>; }'.
        Property 'myField' is incompatible with index signature.
          Type '{ kind: string; }' is not assignable to type 'PlainMessage<Value>'.
            Types of property 'kind' are incompatible.
              Type 'string' is not assignable to type '{ value: number; case: "numberValue"; } | { value: string; case: "stringValue"; } | { value: boolean; case: "boolValue"; } | { case: undefined; value?: undefined; }'.

For an explanation what is generated for oneof groups, please refer to the documentation here. We have some examples for accessing the type here.

In your example, it should probably be something like:

  const rows: PlainMessage<AnalyticsViewRow>[] = result.map((row) => {
      return {
        value: {
          myField: {
            kind: {case: "numberValue", value: 123},
          }
        },
      };
    });

But that is hard to tell without a reproducible example. Closing this, but if you think there is a bug, please feel free to reopen with more information 🙂.

Hi @timostamm why did you close the issue? It's legit to request for a better example but give me time to react to it. Were you not able to test the example above?

I was able to make it work with your example above but by not following autocompletion and just copy-paste your code. The IntelliSense is broken for such cases. I think this can be fixed by providing better types.

why did you close the issue?

Because it was filed as a bug report "Oneof is not constructable in TypeScript" without a reproducible example, and tests in this repository indicate that they are constructable.

Regarding autocompletion, it seems to work for kind:

1

As well as case:

2

Although TypeScript is unable to provide completion for the discriminator:

3

The type of value is narrowed down:

4

This seems to work as expected to me. In general, I think that the hard to parse error messages are by far the biggest downside of mapped types.

I think this can be fixed by providing better types.

What did you have in mind?

This is my experience:

image
image

I think the issue is PlainMessage. TypeScript has problems inferring the type behind [key: string | number]. When I reassign the value to Record<string, PlainMessage<AnalyticsViewRowValue>> before, I get auto-complete also for the discriminator.

    const rows: PlainMessage<AnalyticsViewRow>[] = result.map((row) => {
    const viewRow: Record<string, PlainMessage<AnalyticsViewRowValue>> = {
        a: {
          kind: { case: "stringValue", value: "a" },
        },
      };
  })

or when I just use AnalyticsViewRow without the PlainMessage it works too

image

What's the recommended way?

TypeScript 5.1.6