-
Notifications
You must be signed in to change notification settings - Fork 349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow all fields to be optional #397
Comments
Ah, exact types, where art thou? :-) Your analysis/overview is exactly right, Maybe as a pedantic issue / also curious how go handles this, in ts-proto we don't really have a way of distinguishing the "creating a message" type vs. "reading a message" type (except for That said, for writers, you're right, it's tedious to add Anyway, you're right we've already got And this request comes up often enough, that if you want to just change Thanks for the well-written out thoughts/request. Looking forward to a PR. :-) |
Thank you @stephenh for the response! I've already started work on a PR. It currently adds a
Right -- what's important to know is that in Go, there is no concept of a variable being undefined or uninitialized. When a variable is declared but not initialized, it has the default value for that variable: 0 for numbers, "" for strings, nil for pointers, or [] for arrays. This is also true for members of structs. Since every proto Message becomes a struct with the same members, all members are also "initialized by default". So, without any protobuf magic on the Go side, when you declare a AFAIK, there's no way in Typescript to say "in the interface Foo, id1 should always exist; if you don't initialize it it should read back as 0". I guess we could go all the way and make a class with intercepted getters and setters, but I'm not sure if that's better. Barring that, we're stuck with unset fields being It's not necessarily too bad having to handle "field doesn't exist", though, as Typescript allows writing
If it is really a required field, then yes, but proto3 removed this concept as fields tend to be required until they're not, at which point required fields are difficult to remove. See also this explanation of the discussion which is much better than mine. I think the take-away is that the application code requires a field at some point in time, but the proto definition does not. When reading, you can assume that a field is set (e.g. |
PR is created: #402 |
I'm not sure this is the right way to go. A protobuf message instance has no semantic difference between a field that is not set and a field that has the default value (and it's a pain protobuf.js has this difference). Using field with The way Go allows you to create structs without being explicit of the content of the field is … well … a matter of taste. I never saw this in any other language and don't consider it to be somthing worth exporting. |
@webmaster128 yeah, I agree with your analysis, and it's basically what (after looking through the ts-proto readme again), I articulated one of the last times this came up: #120 (comment) That said, the |
Also, per the SO post, it does have some good points i.e. messages that are persisted, and pushing breaking changes across a wide-variety of systems... Just talking out loud/for my own mental model...I was going to assert that features like That said, there is some nuance where "you can specify optional" (presence) !== "you can specify required" (required). I.e. I think I was treating them as opposites: if I can specify something as optional, that's implying that everything else is "not optional" i.e. "required". But that's not right. I.e. |
Also, shoot, @sgielen you had directly linked to it right there in the PR description, but I'm just how catching up that we tried this before and reverted:
...I was going to naively question "...well, sure, but doesn't ...except, right, it doesn't "add them everywhere", it leaves primitives and repeateds (which do have Sorry guys, it's been awhile since I've paid attention to the deeper nuances here... So, per @webmaster128 's point, the current But, right, this goes much further, and dramatically changes how readers much interact with every field. At the minimum, we need to keep the current |
Thanks for the follow up. Fine with me. I'd just encourage to avoid the overhead in the generated code for users that do not use this new option to be minimal.
To me the biggest question is: will |
Closing this out as #402 got merged, with |
Background
We have a single repository with a Go server and a Typescript client. We use protobufs to communicate between the two. We try to maintain forwards and backwards compatibility between the two, i.e. the client can send an extra field which the server ignores, or the server accepts an extra field that the client does not send yet. Protobuf, in principle, is excellent for this.
The issue
Currently, for proto files shared between Typescript and Go, there is a semantics difference which makes it difficult to add new fields. Suppose we have a
message Foo
with anint64 id = 1;
and we add the additional fieldstring name = 2;
to the proto definition. Now, if we don't set a value for this field explicitly in the Go code, it is simply interpreted as having the default value for that field (e.g. "" for strings). For Typescript code, an error is generated for all instantiations of the message indicating atype mismatch between {id: number} and Foo, you're missing {name: string}
.Current options
The options I'm aware of:
useOptionals=true
ts-proto parameter allows message fields to be optional, but not scalars or repeated fields.StringValue
instead ofstring
, making the scalar field a message field, allowing it to be optional; in combination with the option above, it would allow message fields and scalars to be optional, but not repeated fields.optional
, but not repeated fields. Also, proto3's use case is different: it indicates "field presence" semantics, allowing to see the difference between a default value and an unset value; proto3 already allows all fields to be missing from all protobufs.useOptionals=true
and use type wrappers, the issue would arguably be resolved for scalars, message fields and repeated fields? Is this the preferred way to go?Foo.fromPartial()
, but that means usingfromPartial
everywhere, which is also tedious.It seems to me none of these options truly resolve the issue, they are all workarounds.
The proposal
I would like to add an additional parameter that enables ts-proto to follow Go semantics and make all fields optional. With the option enabled, I would expect the following proto:
to generate the following Typescript (or something similar):
I am aware of the downside that this allows typo's, i.e. I can use
{idd1: 5}
as aFoo
without errors since all fields are optional and Typescript allows setting additional fields (until Exact Types are implemented). Of course, in Go this risk does not exist since you are not allowed to set any fields that don't exist, so you would get an error here. I accept this risk and would like to cover it with better testing.Do you think this is possible, and do you think it is useful to add?
The text was updated successfully, but these errors were encountered: