Skip to content

Update guidance on required field serialization #8486

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

JoelSpeed
Copy link
Contributor

@JoelSpeed JoelSpeed commented Jun 12, 2025

@liggitt and I were discussing serialization of required fields in a thread and realised that there are valid cases where a required field should be a pointer and should be omitempty.

Take the example:

// +required
// +k8s:minimum=0
Foo *int32 `json:"foo,omitempty"`

Here, the value 0 is a valid user choice.

For a structured Go client, if the field were not a pointer, and not omitempty per the existing guidance, then the structured client is not actually required to set a value for the field, as the json marshalling process will marshal foo: 0 and set a choice for them.

This defeats the purpose of the field being required for structured clients, and creates a discrepancy in behaviour between structured and unstructured clients.

Would appreciate thoughts of other API reviewers on this
CC @thockin @deads2k

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Jun 12, 2025
@k8s-ci-robot k8s-ci-robot requested review from dims and jbeda June 12, 2025 09:51
@k8s-ci-robot k8s-ci-robot added the area/developer-guide Issues or PRs related to the developer guide label Jun 12, 2025
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: JoelSpeed
Once this PR has been reviewed and has the lgtm label, please assign dims for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added sig/architecture Categorizes an issue or PR as relevant to SIG Architecture. sig/contributor-experience Categorizes an issue or PR as relevant to SIG Contributor Experience. size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Jun 12, 2025
@JoelSpeed
Copy link
Contributor Author

Thinking through different types we might see, and the implications of this suggestion

Nullable

Anything that needs to be a pointer to allow the zero value to be set, that also has +nullable, does not need to be omitempty. +nullable and omitempty are incompatible.

String

Any required string should have a minimum length. If that minimum length is 0, or unset, then the string should be a pointer+omitempty to allow the empty string to be an explicit choice.

Except when the required string is not actually a string.

  • Enums would not need to be pointer+omitempty unless the empty string is a valid value in the enum
  • Time: Parsing the empty string returns an error when unmarshalling

Integer/Float

As per example in the decsription, if the 0 is in the valid range of values (based on minimum/maximum markers) then pointer and omitempty should be used to allow 0 to be a valid choice.

Bool

Any required bool should be a pointer+omitempty unless validation only allows the value true. (I have no idea if we have that in any APIs?)

Lists

A required list should have a minimum length validation (+k8s:minItems). If that minimum length is 0, or unset, then the list should be a pointer and omitempty (Foo *[]string json:"foo,omitempty"`).

If the list wasn't a pointer, but had omitempty, the structured client would not be able to explicitly set an empty list.

Maps

The same as lists? Not sure what the appropriate marker is for the minimum size of a map (it's +kubebuilder:validation:MinProperties in CRDs)

Objects

For the zero value to be valid, an object should have no required fields, and have no validation that requires a minimum number of fields be set (e.g. +kubeubilder:validationMinProperties, not sure on equivalent +k8s marker). In these cases then the object should be pointer+omitempty to be able to explicitly set the zero value {}.

If the object does have a required field, but that required field would marshal the zero value and that would be accepted by validation, then the zero value of the struct would also be considered valid. I think this would happen generally on a poorly validated set of fields. E.g. a string that doesn't have an explicit minimum length, but has not been added based on the rules outlined here.

The object "bar": {"foo": ""} might be the minimally serialized object, and this would pass the bar and foo validations for being required. But "bar": {} would not pass the validation of foo required, and as such, the object does not need to be a pointer in this case.

@@ -843,10 +843,10 @@ having the `+optional` comment tag is highly recommended.
Required fields have the opposite properties, namely:

- They do not have an `+optional` comment tag.
- They do not have an `omitempty` struct tag.
- They are not a pointer type in the Go definition (e.g. `AnotherFlag SomeFlag`).
- They mark themselves as required explicitly with a `+required` comment tag, or implicitly by not setting an `omitempty` struct tag.
Copy link
Member

@sbueringer sbueringer Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have enough context, but could we simplify this guidance to required fields are only marked via the +required marker (given that's how it should be)

Or at least recommend it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 - explicit FTW. If declarative validation flies, I want explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also believe in being explicit, but I know some tooling is implicitly assuming this, and we have a long history of that implicitness.

If we say they should be explicitly marked, should we also add an N.B. that explains that historically this has been implicitly detected?

Copy link
Member

@sbueringer sbueringer Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we say they should be explicitly marked, should we also add an N.B. that explains that historically this has been implicitly detected?

I was thinking something like this, yes. Recommend to be explicit and then mention in some way how some tools behave (probably these tools (thinking about controller-gen as an example) also won't change to avoid breaking changes)

@JoelSpeed
Copy link
Contributor Author

/hold

I've been through this with @liggitt and we need to make some more updates to the guidance. Will look at creating a decision tree to help folks understand whether or not they should be setting fields as pointers/omitempty etc

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 12, 2025
@liggitt
Copy link
Member

liggitt commented Jun 12, 2025

- The API server should not allow POSTing or PUTing a resource with this field
unset.
- They _typically_ do not use pointer types in the Go definition (e.g. `AnotherFlag SomeFlag`), though required fields where the zero value is a valid value may use pointer types, likely paired with an `omitempty` struct tag to avoid spurious null serializations.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"may" or "must" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is must and likely paired, should remove likely

@JoelSpeed JoelSpeed force-pushed the update-required-fields-conventions branch from c2ee563 to 8856289 Compare June 13, 2025 13:13
@k8s-ci-robot k8s-ci-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Jun 13, 2025
@JoelSpeed JoelSpeed force-pushed the update-required-fields-conventions branch from 8856289 to 22f7099 Compare June 13, 2025 13:14
@JoelSpeed JoelSpeed force-pushed the update-required-fields-conventions branch from 22f7099 to a40a583 Compare June 13, 2025 13:39
@fabriziopandini
Copy link
Member

@JoelSpeed thanks for this PR, this clarifications are really helpful!

- The API server should not allow POSTing or PUTing a resource with this field
unset.
- They _typically_ do not use pointer types in the Go definition (e.g. `AnotherFlag SomeFlag`), though required fields where the zero value is a valid value must use pointer types, paired with an `omitempty` struct tag to avoid spurious null serializations.

For more details on how to use pointers and `omitempty` with fields, see [Serialization of optional/required fields](#serialization-of-optionalrequired-fields).

Using the `+optional` or the `omitempty` tag causes OpenAPI documentation to
Copy link
Member

@fabriziopandini fabriziopandini Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: should this (openAPI documentation) change now that we can have omitempty on required fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to make sure that it respects +required tags. If the field doesn't have +required and has omitempty, then I think there are many tools that still assume optional and we shouldn't change that.

In the long run, we need to make sure all fields are marked as +optional xor +required explicitly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/developer-guide Issues or PRs related to the developer guide cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. sig/architecture Categorizes an issue or PR as relevant to SIG Architecture. sig/contributor-experience Categorizes an issue or PR as relevant to SIG Contributor Experience. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants