Skip to content

feat: Dont make copies of structs for validation #665

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

Closed
wants to merge 1 commit into from

Conversation

asmfreak
Copy link
Contributor

This PR fixes a small problem with validation implementation:
Current implementation uses dst.Interface() which returns a value(T), not a pointer(*T) of a struct T. This can be a performance hit on bigger structures as compiler would have to partially copy the struct T. And since validation is performed on each structure, the whole thing would be copied at least N times (where N is the structure depth).

Passing by value also does not play nicely with other (non-reflection based) validation libraries, such as github.com/go-ozzo/ozzo-validation, since validating a structure there can require a method call, which is not possible. Converting an interface{} with value is not an option also - reflect.Value.Addr() is prohibited for Values, created via reflect.ValueOf(v).

@codecov-commenter
Copy link

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

Attention: Patch coverage is 57.14286% with 3 lines in your changes missing coverage. Please review.

Project coverage is 77.59%. Comparing base (abc7083) to head (c510242).

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #665      +/-   ##
==========================================
- Coverage   77.61%   77.59%   -0.03%     
==========================================
  Files          22       22              
  Lines        7953     7959       +6     
==========================================
+ Hits         6173     6176       +3     
- Misses       1362     1364       +2     
- Partials      418      419       +1     

@asmfreak
Copy link
Contributor Author

I'm not sure, if the if branch is needed, really. Please advise, I'll modify the code

@goccy goccy requested a review from Copilot March 16, 2025 13:20
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR modifies the validation implementation to avoid unnecessary struct copies by ensuring a pointer is used during validation.

  • Updated the test validation helper and added tests to enforce pointer-based validation.
  • Modified the decoding logic to conditionally pass the address of a struct for validation when possible.

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
testdata/validate_test.go Added a testValidator helper and tests for pointer-based validation
decode.go Updated decodeStruct to use pointer when validating a struct to avoid unnecessary copying

Copy link
Collaborator

@shuheiktgw shuheiktgw left a comment

Choose a reason for hiding this comment

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

Thank you for the great PR, and sorry for the delayed response. I’ve left a few comments so PTAL! Since this PR was created a while ago, I’d be happy to take it over from here on top of your commit if that works better for you 🙂

@@ -1425,7 +1425,13 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N
}

if d.validator != nil {
if err := d.validator.Struct(dst.Interface()); err != nil {
var err error
Copy link
Collaborator

Choose a reason for hiding this comment

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

As you already mentioned in your comment, dst is guaranteed to be addressable here, so there's no need to call CanAddr.

if err := d.validator.Struct(dst.Addr().Interface()); err != nil {

return tv.v.Struct(v)
}

func TestStructValidatorWithAPointer(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thank you for adding unit tests here! However, checking whether v is a pointer seems like an unnecessary implementation detail that we don't really care about. This code path is already well covered by the unit tests above, so I believe we don't really need these tests.

@asmfreak
Copy link
Contributor Author

Closing in favour of #737

@asmfreak asmfreak closed this May 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants