Skip to content

Skip null check in doMarshal JSON serializer#6807

Open
RanVaknin wants to merge 5 commits intomasterfrom
rvaknin/json-serializer-null-skip
Open

Skip null check in doMarshal JSON serializer#6807
RanVaknin wants to merge 5 commits intomasterfrom
rvaknin/json-serializer-null-skip

Conversation

@RanVaknin
Copy link
Contributor

@RanVaknin RanVaknin commented Mar 20, 2026

Background

During JSON serialization, JsonProtocolMarshaller.doMarshall() iterates every SdkField on a POJO and calls marshallField() for each one, including fields that are null. marshallField uses a registry to dispatch to a type specific marshaller (string, int, map, null, etc ) based on the field's value.
When the value is null, it dispatches to the NULL marshaller, that checks if the field is required (throw) or not (no-op). For non-required null fields, this entire chain (registry lookup, marshaller resolution, trait check) is wasted work.

The proposed change is to short circuit null handling for payload fields directly on the generic doMarshall() layer, and avoid the dispatch. Non payload fields still delegate to marshallField since their null marshallers have different validation logic.

Boto3's RestJson serializer handles it very similarly.

Testing

Our protocol tests already cover the main code paths of doMarshal (null headers, null query params, partial payload serialization). I added one unit test to specific JsonProtocolMarshaller directly to cover the case where a null required payload field reaches the marshaller. This codepath is caught earlier by the SDK's generated validators, but the this is just make sure the marshaller handles it correctly if that layer is ever bypassed.

Results:

Benchmarks written in #6776

Before:
image

After
image

Protocol Operation Master (ops/µs) Feature (ops/µs) Improvement
RestJson CreateFunction deser 0.176 0.175 -0.57%
RestJson CreateFunction ser 0.284 0.327 15.14%
JSON DynamoDB PutItem deser 0.141 0.141 0.00%
JSON DynamoDB PutItem ser 0.101 0.124 22.77%
CBOR CloudWatch GetMetricData deser 0.382 0.385 0.79%
CBOR CloudWatch GetMetricData ser 0.416 0.457 9.86%

@RanVaknin RanVaknin changed the title Rvaknin/json serializer null skip skip null check in doMarshal JSON serializer Mar 20, 2026
@RanVaknin RanVaknin changed the title skip null check in doMarshal JSON serializer Skip null check in doMarshal JSON serializer Mar 23, 2026
@RanVaknin RanVaknin marked this pull request as ready for review March 23, 2026 18:28
@RanVaknin RanVaknin requested a review from a team as a code owner March 23, 2026 18:28
import software.amazon.awssdk.protocols.json.AwsJsonProtocolMetadata;
import software.amazon.awssdk.protocols.json.internal.AwsStructuredPlainJsonFactory;

class JsonProtocolMarshallerTest {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Almost all of the codepaths that this file tests are covered by the protocol test package. nullPayloadField_required_throwsIllegalArgumentException is the only test that is not covered by the end to end tests because its technically not possible to get there. This codepath branch was likely added as a defense in depth.

Comment on lines +220 to +222
} else if (field.containsTrait(RequiredTrait.class, TraitType.REQUIRED_TRAIT)) {
throw new IllegalArgumentException(
String.format("Parameter '%s' must not be null", field.locationName()));
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we validate required client side? I thought we generally didn't do client side validation of required and instead just sent these requests to the service (since the api may have evolved and removed the required over time).

Copy link
Contributor Author

@RanVaknin RanVaknin Mar 23, 2026

Choose a reason for hiding this comment

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

I agree, but this is not a new validation. I had to port this logic out from the null marshaller:

https://github.com/aws/aws-sdk-java-v2/blob/master/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/SimpleTypeJsonMarshaller.java#L42

since we are handling nulls now at the doMarshal level.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see - yeah, agree this makes sense here then.

Comment on lines +218 to 219
} else if (field.location() != MarshallLocation.PAYLOAD) {
marshallField(field, val);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any reason we added field.location() != MarshallLocation.PAYLOAD check here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this optimization only applies to PAYLOAD fields. Non payload locations (path, header, query) have their own null marshallers with their own behavior. If we removed the != MarshallLocation.PAYLOAD check and skipped all null fields, null path params would be silently dropped instead of throwing, which broke RestJsonExceptionTests.nullPathParam_ThrowsSdkClientException in an earlier iteration of this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, it's not very obvious from looking at the code, can we add a comment in code explaining this?

Copy link
Contributor

@alextwoods alextwoods left a comment

Choose a reason for hiding this comment

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

Overall looks good. Needs a changelog I think.

@sonarqubecloud
Copy link

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