-
Notifications
You must be signed in to change notification settings - Fork 4
Empty values serialising to the zero value is semantically confusing #2
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
Comments
Thanks for reporting this. This is a bug I think. The intent is for this library to handle the empty state well, and the marshal step should be treating the final value as if it's a pointer: empty is no value/null for all types. Let me see how we can fix this in a safe predictable way for all types. |
I want to make sure I understand the problem clearly before I jump into the PR and into how this should be fixed. I'm using this example to explore the problem: https://go.dev/play/p/E4TxEKZhwhE
1a and 2a are relatively self explanatory. When a time value is set we want that to be serialized. 1b and 2b are less obvious. A time value is still set within the optional. If we think about the option as either "none" or "some value" the optional is in the "some value" state. The optional package cannot know for every type if its "some value" should also be treated as "none" because some types default / zero value is meaningful. 1c and 2c I think highlight the bug. The optional is empty and so in 2c it correctly renders to an omitted field. The optional is empty for 1c also but it still renders the time, which doesn't make much sense. In this situation I would expect Could you confirm if 1c is the case you're seeing as a bug? |
Hey Leigh, sorry for the late response! Yeah, case 1-c is what I'm seeing as the bug here. omitempty is useful for avoiding this and we've done that in a couple of places before I used my fork (we make use of code generation tools a lot and sometimes it's not easy to add omitempty to every field) For b cases, I actually wrote an 2-c is correct by omitting which is controlled by the JSON library itself so 1-c is the only problem here, an empty value serialising as a zero value instead of null. (it would be preferable to omit entirely, but as mentioned it's not possible to do this from the default JSON library) Thanks! I've opened a PR with the change, though my coworker also pushed some changes to that fork so I could move the fix for this issue onto a separate branch if necessary. |
Thank for confirming. I've been pondering the behavior, and why I implemented it this way, and all the options that exist for how 1c could behave. I agree, null is more appropriate in the 1c case, but I wanted to write out my thinking in full. There are three ways the 1c case, when the optional is empty, could behave:
|
Great library! So much easier than pointers all over the place. We use this pretty heavily in our codebase and it's been mostly fine however this behaviour is causing some confusing outcomes when it comes to JSON serialisation and consumption by TypeScript.
One nice simple yet annoying side of Go is the use of zero values to indicate emptiness. This is fine for some values but structs are problematic.
The issue I've just noticed is
optional.Optional[time.Time]
this serialises to:Whereas the expected result from the perspective of a JSON consumer (in any language) is one of:
Or, simply:
Where
result.date
would yieldundefined
and be typed as:(Which we generate from Go structs using Supervillain)
Given that use-case, I propose that
MarshalJSON
be implemented in a v2 (breaking change) as:(There's no way to omit a field via MarshalJSON yet, see: golang/go#50480)
The text was updated successfully, but these errors were encountered: