Skip to content
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

Optimized Jackson Deserialization for Optimized List of Primitives #2431

Merged
merged 2 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/@unreleased/pr-2431.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: improvement
improvement:
description: Optimize Jackson deserialization when deserializing optimized lists
of primitives.
links:
- https://github.com/palantir/conjure-java/pull/2431

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -601,11 +601,12 @@ private Iterable<MethodSpec> createSetters(
Map<com.palantir.conjure.spec.TypeName, TypeDefinition> typesMap,
boolean override,
boolean strict) {
Collection<MethodSpec> setters = Lists.newArrayListWithExpectedSize(fields.size());
// Each field tends to fan out into many setters
Copy link
Contributor

Choose a reason for hiding this comment

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

This is only true of container types (optional/set/list), not standard fields. Difficult to tell whether a 5x multiplier is ideal or not, but probably not worth spending much time optimizing

Collection<MethodSpec> setters = Lists.newArrayListWithExpectedSize(fields.size() * 5);
for (EnrichedField field : fields) {
setters.add(createSetter(field, typesMap, override));
if (!strict || field.conjureDef().getType().accept(TypeVisitor.IS_OPTIONAL)) {
setters.addAll(createAuxiliarySetters(field, override));
setters.addAll(createAuxiliarySetters(field, typesMap, override));
}
}
return setters;
Expand All @@ -617,23 +618,6 @@ private MethodSpec createSetter(
boolean override) {
FieldSpec field = enriched.poetSpec();
Type type = enriched.conjureDef().getType();
AnnotationSpec.Builder annotationBuilder = AnnotationSpec.builder(JsonSetter.class)
.addMember("value", "$S", enriched.fieldName().get());
if (type.accept(TypeVisitor.IS_OPTIONAL)) {
annotationBuilder.addMember("nulls", "$T.SKIP", Nulls.class);
} else if (isCollectionType(type)) {
annotationBuilder.addMember("nulls", "$T.SKIP", Nulls.class);
if (isOptionalInnerType(type)) {
annotationBuilder.addMember("contentNulls", "$T.AS_EMPTY", Nulls.class);
} else if (options.nonNullCollections()) {
annotationBuilder.addMember("contentNulls", "$T.FAIL", Nulls.class);
}
} else if (type.accept(TypeVisitor.IS_REFERENCE)) {
Type dealiased = TypeFunctions.toConjureTypeWithoutAliases(type, typesMap);
if (dealiased.accept(DefaultableTypeVisitor.INSTANCE)) {
annotationBuilder.addMember("nulls", "$T.AS_EMPTY", Nulls.class);
}
}

boolean shouldClearFirst = true;
MethodSpec.Builder setterBuilder = BeanBuilderAuxiliarySettersUtils.publicSetter(enriched, builderClass)
Expand All @@ -651,11 +635,39 @@ private MethodSpec createSetter(
setterBuilder.addCode("this.$L = true;", deriveFieldInitializedName(enriched));
}

return setterBuilder
.addStatement("return this")
.addAnnotations(ConjureAnnotations.override(override))
.addAnnotation(annotationBuilder.build())
.build();
setterBuilder.addStatement("return this").addAnnotations(ConjureAnnotations.override(override));

// Certain type combinations and feature flags may produce highly optimized code paths.
// These optimized paths include deserialization, so if it isn't enabled we should add the
// simple deserializer.
if (!isPrimitiveOptimized(type)) {
setterBuilder.addAnnotation(createJacksonSetterAnnotation(enriched, typesMap));
}

return setterBuilder.build();
}

private AnnotationSpec createJacksonSetterAnnotation(
EnrichedField enriched, Map<com.palantir.conjure.spec.TypeName, TypeDefinition> typesMap) {
Type type = enriched.conjureDef().getType();
AnnotationSpec.Builder annotationBuilder = AnnotationSpec.builder(JsonSetter.class)
.addMember("value", "$S", enriched.fieldName().get());
if (type.accept(TypeVisitor.IS_OPTIONAL)) {
annotationBuilder.addMember("nulls", "$T.SKIP", Nulls.class);
} else if (isCollectionType(type)) {
annotationBuilder.addMember("nulls", "$T.SKIP", Nulls.class);
if (isOptionalInnerType(type)) {
annotationBuilder.addMember("contentNulls", "$T.AS_EMPTY", Nulls.class);
} else if (options.nonNullCollections()) {
annotationBuilder.addMember("contentNulls", "$T.FAIL", Nulls.class);
}
} else if (type.accept(TypeVisitor.IS_REFERENCE)) {
Type dealiased = TypeFunctions.toConjureTypeWithoutAliases(type, typesMap);
if (dealiased.accept(DefaultableTypeVisitor.INSTANCE)) {
annotationBuilder.addMember("nulls", "$T.AS_EMPTY", Nulls.class);
}
}
return annotationBuilder.build();
}

private MethodSpec createCollectionSetter(String prefix, EnrichedField enriched, boolean override) {
Expand All @@ -671,14 +683,19 @@ private MethodSpec createCollectionSetter(String prefix, EnrichedField enriched,
.build();
}

private MethodSpec createPrimitiveCollectionSetter(EnrichedField enriched, boolean override) {
private MethodSpec createPrimitiveCollectionSetter(
EnrichedField enriched,
Map<com.palantir.conjure.spec.TypeName, TypeDefinition> typesMap,
boolean override) {
FieldSpec field = enriched.poetSpec();
Type type = enriched.conjureDef().getType();

CollectionType collectionType = getCollectionType(type);
return BeanBuilderAuxiliarySettersUtils.createPrimitiveCollectionSetterBuilder(
enriched, typeMapper, builderClass, safetyEvaluator)
.addAnnotations(ConjureAnnotations.override(override))
// Unlike the other setter, when this setter is present it is always responsible for deserialization
.addAnnotation(createJacksonSetterAnnotation(enriched, typesMap))
.addCode(verifyNotBuilt())
.addCode(CodeBlocks.statement(
"$1T.addAllTo$2L(this.$3N, $4L)",
Expand Down Expand Up @@ -783,7 +800,10 @@ private boolean isByteBuffer(Type type) {
return type.accept(TypeVisitor.IS_BINARY) && !options.useImmutableBytes();
}

private List<MethodSpec> createAuxiliarySetters(EnrichedField enriched, boolean override) {
private List<MethodSpec> createAuxiliarySetters(
EnrichedField enriched,
Map<com.palantir.conjure.spec.TypeName, TypeDefinition> typesMap,
boolean override) {
Type type = enriched.conjureDef().getType();
Optional<LogSafety> safety = safetyEvaluator.getUsageTimeSafety(enriched.conjureDef());

Expand All @@ -793,7 +813,7 @@ private List<MethodSpec> createAuxiliarySetters(EnrichedField enriched, boolean
CollectionType collectionType = getCollectionType(type);
if (collectionType.getConjureCollectionType().isPrimitiveCollection()
&& collectionType.useNonNullFactory()) {
builder.add(createPrimitiveCollectionSetter(enriched, override));
builder.add(createPrimitiveCollectionSetter(enriched, typesMap, override));
}

builder.add(
Expand Down Expand Up @@ -1062,11 +1082,11 @@ private static final class CollectionType {
this.nullHandlingMode = nullHandlingMode;
}

public ConjureCollectionType getConjureCollectionType() {
ConjureCollectionType getConjureCollectionType() {
return conjureCollectionType;
}

public boolean useNonNullFactory() {
boolean useNonNullFactory() {
return nullHandlingMode.shouldUseNonNullFactory();
}
}
Expand Down Expand Up @@ -1097,15 +1117,21 @@ private enum ConjureCollectionType {
this.primitiveCollection = primitiveCollection;
}

public String getCollectionName() {
String getCollectionName() {
return collectionName;
}

public Boolean isPrimitiveCollection() {
Boolean isPrimitiveCollection() {
return primitiveCollection;
}
}

private boolean isPrimitiveOptimized(Type type) {
return type.accept(TypeVisitor.IS_LIST)
// Check above guards the getCollectionType call below
&& getCollectionType(type).getConjureCollectionType().isPrimitiveCollection();
}

private enum ConjureCollectionNullHandlingMode {
NON_NULL_COLLECTION_FACTORY(true),
NULLABLE_COLLECTION_FACTORY(false);
Expand All @@ -1116,7 +1142,7 @@ private enum ConjureCollectionNullHandlingMode {
this.useNonNullFactory = useNonNullFactory;
}

public boolean shouldUseNonNullFactory() {
boolean shouldUseNonNullFactory() {
return useNonNullFactory;
}
}
Expand Down