From 4c57a0db1553119f3908c39154a8e63f816ac890 Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Fri, 21 Aug 2026 14:55:30 +0000 Subject: [PATCH 1/9] add AGENTS.md --- AGENTS.md | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..e35e5615a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,123 @@ +# Repository guidance for coding agents + +## Scope and priorities + +- This file applies to the entire repository. If a more specific `AGENTS.md` is added below a module, follow that file for work in that subtree. +- Solve the requested problem completely, add or update tests, and validate the smallest relevant scope before handing off. +- Preserve unrelated work in the checkout. Inspect `git status` before and after changes, and do not rewrite user-owned changes. +- Use existing project patterns and production implementations; do not replace behavior with mocks, placeholders, or test-only shortcuts. + +## Repository overview + +This is a Gradle multi-project Java repository for generating Java bindings from Conjure API definitions. + +- `conjure-java-core`: the main Conjure object, service, Dialogue, Jersey, and Undertow source generators. Most generator logic and golden/snapshot tests live here. +- `conjure-java`: the command-line application, with `ConjureJavaCli` as its entry point. +- `conjure-lib`: shared runtime types used by generated code and other modules. +- `conjure-undertow-lib`, `conjure-java-undertow-runtime`: Undertow APIs and runtime behavior. +- `conjure-undertow-annotations`, `conjure-undertow-processor`, and `conjure-undertow-processor-example`: annotation API, annotation processor, and its integration example. +- `conjure-java-client-verifier` and `conjure-java-server-verifier`: compatibility verification against Conjure verification test cases. +- `versions.props` and `versions.lock`: centralized dependency declarations and their resolved lock state. + +Consult `readme.md` for supported generator features and wire-format expectations. Treat `build.gradle` and `settings.gradle` as authoritative when older documentation disagrees with the current build. + +## Build and toolchains + +- Always invoke the checked-in wrapper from the repository root: `./gradlew ...`. +- The build configures and may provision multiple JDKs. At present it compiles with Java 25, targets Java 17 libraries, runs tests on Java 25, and uses a Java 21 Gradle daemon. A first Gradle invocation may therefore download toolchains. +- Java compilation uses `-Werror`, Error Prone, Checkstyle, dependency checks, and Palantir Java Format. Fix findings rather than weakening or globally suppressing checks. +- Format Java and Gradle sources with `./gradlew format`. Do not manually reformat generated fixtures that are intentionally excluded from formatting. +- Keep dependencies in `versions.props`; do not add inline versions to module build files. Regenerate `versions.lock` with the appropriate Gradle consistent-versions task when dependency declarations change, and review the resulting lock diff. +- `.circleci/config.yml` is generated from `.circleci/template.sh`; do not edit the generated configuration directly. + +## Generated code and snapshots + +Generator output is part of the test contract and is checked into the repository. + +- `conjure-java-core/src/integrationInput/java` contains generated integration fixtures. +- Java-like files under `src/test/resources` include golden outputs for generators and the Undertow annotation processor. +- A normal test run compares generated output with these files. If a generator change intentionally alters output, run `./gradlew test -Drecreate=true`, then carefully review every changed and newly created fixture. +- Never use `-Drecreate=true` merely to make a failing snapshot test pass. Confirm that each output change follows from the intended production change, then rerun the affected tests without recreation. +- Generator changes should normally cover representative combinations of options, not only a single happy-path fixture. Update the parameterized cases or processor samples where appropriate. + +## Implementation conventions + +- Follow the existing Java style and copyright headers in neighboring files. +- Preserve Conjure wire compatibility and generated API semantics: generated values are immutable, fields are non-null, optionals represent absence, enums and unions tolerate unknown values, and collection behavior depends on generator options. +- Keep generator option behavior consistent across objects and relevant service backends (Dialogue, Jersey, and Undertow). When adding an option, wire it through `Options`, CLI/configuration handling, generation, documentation, and snapshots as applicable. +- Prefer focused, package-local changes. Avoid exposing a new public API when an existing internal abstraction is sufficient. +- Published library modules use API compatibility checks. Do not update compatibility baselines simply to silence a failure; first determine whether the API change is intended. +- Tests use JUnit 5 and AssertJ. Annotation-processor tests also use compile-testing and checked-in generated sources. Match the style of adjacent tests. + +## Validation + +Run validation in increasing scope, choosing tasks that cover every modified module: + +```bash +./gradlew :conjure-java-core:test --tests 'fully.qualified.TestClass' +./gradlew :conjure-java-core:test +./gradlew :conjure-undertow-processor:test +./gradlew format +./gradlew build +``` + +- Use the analogous `::test` task for other modules; do not run unrelated verifier suites for a narrowly scoped change unless shared behavior is affected. +- Run the full `./gradlew build` for cross-module, dependency, generator-output, or public-API changes. CI runs the build with parallel workers and fails if it leaves tracked files modified. +- After formatting, fixture recreation, or a full build, inspect `git status --short` and the complete diff. Generated output or lock-file changes must be included only when intentional. +- If environment or toolchain setup prevents a task from running, report the exact command and failure; do not claim the task passed. + +## Change hygiene + +- Keep changes narrowly related to the request and include regression coverage for bug fixes. +- Update `readme.md` when user-visible flags or documented generator behavior changes. +- User-facing changes may require a changelog entry under `changelog/@unreleased`; follow the existing changelog YAML schema and repository release conventions. +- In the final handoff, summarize behavior changed, tests run, and any validation that could not be completed. + +## Code Conventions + +### Code Style +- All files must end with a newline +- Use `./gradlew spotlessApply` to auto-format code (Palantir Java format) +- Compiler treats warnings as errors (`-Werror`) +- Prefer imported classes over fully qualified names (`List` not `java.util.List`) +- All dependencies must be explicitly declared (no implicit transitive dependencies) +- Java library target: 17, distribution target: 21 +- Avoid unnecessary comments - most code is self-documenting, only explain non-obvious details or high-level concepts (applies to javadoc and inline comments). When you do include comments, make sure they are concise. +- **String concatenation whitespace** - When a string literal is split across multiple lines (typically by a formatter or for readability), ensure the boundary between adjacent literals preserves required whitespace. Place a leading space at the start of the second segment (e.g., `"first part" + " second part"`). Applies to error messages, log messages, exception arguments, test strings, and any other user- or developer-visible concatenated text. + +### Safe Logging +- Use the `com.palantir.logsafe` safe-logging library to ensure log safety. Exception messages and arguments must be explicitly marked as safe or unsafe so that logging systems (e.g., Grafana) never leak sensitive data. Classes and parameters can also carry `@Safe` or `@Unsafe` annotations to declare their safety at the type level. +- **Exceptions**: Never throw raw JDK exceptions (`IllegalStateException`, `IllegalArgumentException`, `NullPointerException`, `RuntimeException`, etc.). Use their safe-logging equivalents from `com.palantir.logsafe.exceptions` instead: `SafeIllegalStateException`, `SafeIllegalArgumentException`, `SafeNullPointerException`, `SafeRuntimeException`, `SafeUncheckedIoException`, etc. + - **Typed Conjure errors are exempt**: Conjure-defined error types already declare the safety of each argument via `safe-args` and `unsafe-args` fields, so they don't need `Safe*` equivalents. +- **Exception arguments**: Pass structured arguments using `SafeArg.of("name", value)` (for safe-to-log values) or `UnsafeArg.of("name", value)` (for sensitive values) instead of interpolating values into the message string. When choosing `SafeArg` vs `UnsafeArg`: + - **Safe**: RIDs with random/UUID locators (e.g. object type RIDs, dataset RIDs), durations, counts, class names + - **Unsafe**: User-inputted string identifiers (e.g. object type IDs, branch names/IDs, property type IDs, search queries), exception messages, file paths, PII + - **Do not log**: Credentials, tokens, private keys, authorization headers + - When in doubt, use `UnsafeArg` and flag it for the author to review +- **Preconditions**: Use `com.palantir.logsafe.Preconditions` instead of Guava's `com.google.common.base.Preconditions` for the same safety guarantees. +- **Logging**: Use `SafeLogger` / `SafeLoggerFactory` instead of SLF4J's `LoggerFactory` directly. + +### Streams and Optionals +- Use `Stream.toList()` instead of `Stream.collect(Collectors.toList())` where possible. +- **Small streams (default)**: Readability wins. + - When using optionals in a stream, prefer `.mapMulti(Optional::ifPresent)` over `.flatMap(Optional::stream)`. + - No `.map(Optional::get).filter(Objects::nonNull)`. It has 2 steps which is more error-prone. +- **Large streams or store-level code**: Allocation wins. For nullable values, prefer either: + - `.map(...).filter(Objects::nonNull)` directly on the nullable value, or + - A plain `for`/`forEach` loop instead of `stream().map(...)` when the pipeline is simple. + +### Union Types +- For sealed unions (Conjure union types or sealed interfaces): + - Use `switch` expressions when handling multiple variants differently (ensures exhaustiveness) + - Use `instanceof` for guard clauses where only one variant needs handling and others early-return + +### Collections +- Prefer Guava's immutable collections (`ImmutableSet`, `ImmutableList`, `ImmutableMap`) with builder pattern over mutable collection classes (`LinkedHashSet`, `ArrayList`, `HashMap +`). + - JDK immutable utilities like `Set.of()` or `List.of()` are fine. +- **Method boundaries vs. internal use**: An immutable or unmodifiable view of a collection is strongly preferred at method boundaries (e.g., return types and parameters). For inter +mediate results within a method scope (e.g., a collection immediately passed into a builder), using `Collectors.toSet()` or similar mutable collector is acceptable for efficiency. + +### Testing Conventions +- Prefer Mockito's `eq()` matchers over `any()` for higher test coverage, unless the assertion is not relevant for the test. +- Name the `@BeforeEach` setup method `before`. From c71bcfa3e02de9af927eb5a38d67ca9419043c18 Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Fri, 21 Aug 2026 16:46:13 +0000 Subject: [PATCH 2/9] Optimize generated Jackson handling --- conjure-java-client-verifier/build.gradle | 2 +- conjure-java-core/build.gradle | 2 + .../java/types/BeanBuilderGenerator.java | 25 +- .../conjure/java/types/UnionGenerator.java | 374 +++++++++++++++--- conjure-java-server-verifier/build.gradle | 2 +- 5 files changed, 337 insertions(+), 68 deletions(-) diff --git a/conjure-java-client-verifier/build.gradle b/conjure-java-client-verifier/build.gradle index 3bddaa15d..1bab56089 100644 --- a/conjure-java-client-verifier/build.gradle +++ b/conjure-java-client-verifier/build.gradle @@ -67,6 +67,7 @@ project('verification-server-api') { api 'com.palantir.dialogue:dialogue-serde' implementation 'com.fasterxml.jackson.core:jackson-annotations' + implementation 'com.fasterxml.jackson.core:jackson-core' implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.google.code.findbugs:jsr305' implementation 'com.google.errorprone:error_prone_annotations' @@ -84,4 +85,3 @@ tasks.withType(JavaCompile).matching { it.name == "compileTestJava" }.configureE check('Slf4jLogsafeArgs', net.ltgt.gradle.errorprone.CheckSeverity.OFF) } } - diff --git a/conjure-java-core/build.gradle b/conjure-java-core/build.gradle index 91ac604f7..a968a4df1 100644 --- a/conjure-java-core/build.gradle +++ b/conjure-java-core/build.gradle @@ -53,6 +53,7 @@ dependencies { implementation project(':conjure-lib') implementation 'com.fasterxml.jackson.core:jackson-annotations' + implementation 'com.fasterxml.jackson.core:jackson-core' implementation 'com.google.code.findbugs:jsr305' implementation 'com.google.errorprone:error_prone_annotations' implementation 'com.palantir.common:streams' @@ -97,6 +98,7 @@ dependencies { integrationInputImplementation project(':conjure-lib') integrationInputImplementation project(':conjure-undertow-lib') integrationInputImplementation 'com.fasterxml.jackson.core:jackson-annotations' + integrationInputImplementation 'com.fasterxml.jackson.core:jackson-core' integrationInputImplementation 'com.fasterxml.jackson.core:jackson-databind' integrationInputImplementation 'com.google.code.findbugs:jsr305' integrationInputImplementation 'com.google.errorprone:error_prone_annotations' diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanBuilderGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanBuilderGenerator.java index 5854ad671..8f697fbe4 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanBuilderGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanBuilderGenerator.java @@ -193,7 +193,7 @@ private void addStagedBuilderImpl( List allFields = enrichFields(typeDef.getFields()); List fieldsNeedingBuilderStage = allFields.stream() .filter(field -> !BeanBuilderGenerator.stagedBuilderFieldShouldBeInFinalStage(field)) - .collect(Collectors.toList()); + .toList(); if (fieldsNeedingBuilderStage.isEmpty()) { addBuilderImpl(specBuilder, typeDef, typesMap); @@ -206,7 +206,7 @@ private void addStagedBuilderImpl( interfaces.add(generateBuilderFinalStageInterface( allFields.stream() .filter(field -> !fieldsNeedingBuilderStage.contains(field)) - .collect(Collectors.toList()), + .toList(), false)); specBuilder.addTypes(interfaces); addBuilderInterfaceAndMethod(specBuilder, interfaces); @@ -234,7 +234,7 @@ private void addBuilderInterfaceAndMethod(TypeSpec.Builder specBuilder, List ClassName.get(objectClass.packageName(), objectClass.simpleName(), stageInterface.name())) .map(Primitives::box) - .collect(Collectors.toList()); + .toList(); TypeSpec builderInterface = TypeSpec.interfaceBuilder(STAGED_BUILDER_INTERFACE_NAME) .addModifiers(Modifier.PUBLIC) @@ -251,7 +251,7 @@ private void addBuilderInterfaceAndMethod(TypeSpec.Builder specBuilder, List fields, .addMethods(fields.stream() .map(field -> generateMethodsForFinalStageInterfaceField(field, completedStageClass, strict)) .flatMap(List::stream) - .collect(Collectors.toList())) + .toList()) .build(); } @@ -423,6 +423,13 @@ private TypeSpec generateBuilderImplementation( boolean strict) { Collection enrichedFields = enrichFields(typeDef.getFields()); Collection poetFields = EnrichedField.toPoetSpecs(enrichedFields); + Collection builderFields = enrichedFields.stream() + .map(field -> field.conjureDef().getType().accept(TypeVisitor.IS_MAP) + ? field.poetSpec().toBuilder() + .addAnnotation(createJacksonSetterAnnotation(field, typesMap)) + .build() + : field.poetSpec()) + .toList(); TypeSpec.Builder specBuilder = TypeSpec.classBuilder(className); boolean implementsInterface = builderInterfaceClass.isPresent(); @@ -438,7 +445,7 @@ private TypeSpec generateBuilderImplementation( .addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(BeanBuilderGenerator.class)) .addModifiers(Modifier.STATIC, Modifier.FINAL) .addField(FieldSpec.builder(boolean.class, BUILT_FIELD).build()) - .addFields(poetFields) + .addFields(builderFields) .addFields(primitivesInitializedFields(enrichedFields)) .addMethod(createConstructor()) .addMethod(createFromObject(enrichedFields, implementsInterface)) @@ -457,7 +464,7 @@ private TypeSpec generateBuilderImplementation( private Collection maybeCreateValidateFieldsMethods(Collection enrichedFields) { List primitives = - enrichedFields.stream().filter(EnrichedField::isPrimitive).collect(Collectors.toList()); + enrichedFields.stream().filter(EnrichedField::isPrimitive).toList(); if (primitives.isEmpty()) { return Collections.emptyList(); @@ -522,7 +529,7 @@ private Collection primitivesInitializedFields(Collection FieldSpec.builder(TypeName.BOOLEAN, deriveFieldInitializedName(field), Modifier.PRIVATE) .initializer("false") .build()) - .collect(Collectors.toList()); + .toList(); } private static String deriveFieldInitializedName(EnrichedField field) { @@ -655,7 +662,7 @@ private MethodSpec createSetter( // 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)) { + if (!isPrimitiveOptimized(type) && !type.accept(TypeVisitor.IS_MAP)) { setterBuilder.addAnnotation(createJacksonSetterAnnotation(enriched, typesMap)); } diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java index f7c273462..a6d0a37e5 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java @@ -21,12 +21,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import com.google.common.collect.PeekingIterator; @@ -47,6 +58,7 @@ import com.palantir.conjure.spec.UnionDefinition; import com.palantir.conjure.visitor.TypeVisitor; import com.palantir.javapoet.AnnotationSpec; +import com.palantir.javapoet.ArrayTypeName; import com.palantir.javapoet.ClassName; import com.palantir.javapoet.CodeBlock; import com.palantir.javapoet.FieldSpec; @@ -57,9 +69,11 @@ import com.palantir.javapoet.TypeName; import com.palantir.javapoet.TypeSpec; import com.palantir.javapoet.TypeVariableName; +import com.palantir.javapoet.WildcardTypeName; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -96,6 +110,8 @@ public final class UnionGenerator { private static final String SEALED_KNOWN_INTERFACE = "Known"; private static final String SEALED_UNKNOWN_VARIANT_NAME = "Unknown"; + private static final String DESERIALIZER_CLASS_NAME = "Deserializer"; + private static final String SERIALIZER_CLASS_NAME = "Serializer"; public static JavaFile generateUnionType( TypeMapper typeMapper, @@ -126,8 +142,8 @@ public static JavaFile generateUnionType( typeDef.getTypeName().getName()) .addAnnotations(safety) .addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(UnionGenerator.class)) - .addAnnotation(generateJsonTypeInfo(unknownVariant)) - .addAnnotation(generateJsonSubTypes(unionClass, typeDef.getUnion())) + .addAnnotation(generateJsonDeserialize(unionClass)) + .addAnnotation(generateJsonSerialize(unionClass)) .addAnnotation(ignoreUnknownAnnotation()) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT, Modifier.SEALED) .addPermittedSubclasses(typeDef.getUnion().stream() @@ -140,7 +156,9 @@ public static JavaFile generateUnionType( .addMethods(generateSealedThrowOnUnknown(unionClass, unknownVariant, typeDef.getUnion())) .addTypes(generateWrapperClasses( typeMapper, typesMap, unionClass, visitorClass, typeDef.getUnion(), options)) - .addType(generateUnknownWrapper(unionClass, visitorClass, options)); + .addType(generateUnknownWrapper(unionClass, visitorClass, options)) + .addType(generateSerializer(unionClass)) + .addType(generateDeserializer(unionClass, typeDef.getUnion(), options)); typeDef.getDocs().ifPresent(docs -> typeBuilder.addJavadoc("$L", Javadoc.render(docs))); @@ -175,6 +193,7 @@ public static JavaFile generateUnionType( typeDef.getTypeName().getName()) .addAnnotations(safety) .addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(UnionGenerator.class)) + .addAnnotation(generateJsonDeserialize(unionClass)) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addFields(fields) .addMethod(generateConstructor(baseClass)) @@ -193,6 +212,7 @@ public static JavaFile generateUnionType( .addTypes(generateWrapperClasses( typeMapper, typesMap, baseClass, visitorClass, typeDef.getUnion(), options)) .addType(generateUnknownWrapper(baseClass, visitorClass, options)) + .addType(generateDeserializer(unionClass, typeDef.getUnion(), options)) .addMethod(MethodSpecs.createEquals(unionClass)) .addMethod(MethodSpecs.createEqualTo(unionClass, fields)) .addMethod(MethodSpecs.createHashCode(fields)) @@ -236,33 +256,16 @@ private static MethodSpec createLegacyToStringForSealedUnions(ClassName baseClas .build(); } - private static AnnotationSpec generateJsonTypeInfo(ClassName unknownVariant) { - return AnnotationSpec.builder(JsonTypeInfo.class) - .addMember("use", "JsonTypeInfo.Id.NAME") - .addMember("include", "JsonTypeInfo.As.EXISTING_PROPERTY") - .addMember("property", "\"type\"") - .addMember("visible", "$L", true) - .addMember("defaultImpl", "$T.class", unknownVariant) + private static AnnotationSpec generateJsonDeserialize(ClassName unionClass) { + return AnnotationSpec.builder(JsonDeserialize.class) + .addMember("using", "$T.class", unionClass.nestedClass(DESERIALIZER_CLASS_NAME)) .build(); } - private static AnnotationSpec generateJsonSubTypes(ClassName unionClass, List memberTypeDefs) { - List subAnnotations = memberTypeDefs.stream() - .map(memberTypeDef -> AnnotationSpec.builder(JsonSubTypes.Type.class) - .addMember("value", "$T.class", sealedVariantClass(unionClass, memberTypeDef.getFieldName())) - .addMember( - "name", - "$S", - // "unknown" is valid here since UnknownVariant is only used as a default - memberTypeDef.getFieldName().get()) - .build()) - .toList(); - AnnotationSpec.Builder annotationBuilder = AnnotationSpec.builder(JsonSubTypes.class); - subAnnotations.forEach(subAnnotation -> annotationBuilder.addMember("value", "$L", subAnnotation)); - if (subAnnotations.isEmpty()) { - annotationBuilder.addMember("value", "{}"); - } - return annotationBuilder.build(); + private static AnnotationSpec generateJsonSerialize(ClassName unionClass) { + return AnnotationSpec.builder(JsonSerialize.class) + .addMember("using", "$T.class", unionClass.nestedClass(SERIALIZER_CLASS_NAME)) + .build(); } private static AnnotationSpec ignoreUnknownAnnotation() { @@ -829,6 +832,277 @@ private static Stream sortedStageNameTypePairs(Map memberTypeDefs, Options options) { + ClassName deserializerClass = unionClass.nestedClass(DESERIALIZER_CLASS_NAME); + TypeSpec.Builder builder = TypeSpec.classBuilder(deserializerClass) + .addModifiers(Modifier.STATIC, Modifier.FINAL) + .superclass(ParameterizedTypeName.get(ClassName.get(JsonDeserializer.class), unionClass)) + .addSuperinterface(ResolvableDeserializer.class) + .addField(generateVariantTypesField(unionClass, memberTypeDefs, options)) + .addField(generateDeserializersField()) + .addMethod(generateResolveMethod()) + .addMethod(generateIsCachableMethod()) + .addMethod(generateDeserializeMethod(unionClass)) + .addMethod(generateDeserializeBufferedMethod(unionClass)) + .addMethod(generateIsTypeFieldMethod()) + .addMethod(generateDeserializeSelectedMethod(unionClass, memberTypeDefs, options)) + .addMethod(generateResolveDeserializerMethod()) + .addMethod(generateDeserializeUnknownMethod(unionClass, options)); + return builder.build(); + } + + private static FieldSpec generateVariantTypesField( + ClassName unionClass, List memberTypeDefs, Options options) { + TypeName classType = + ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Object.class)); + CodeBlock.Builder initializer = CodeBlock.builder().add("new $T[] {$>", Class.class); + for (int index = 0; index < memberTypeDefs.size(); index++) { + FieldDefinition memberTypeDef = memberTypeDefs.get(index); + ClassName wrapperClass = options.sealedUnions() + ? sealedVariantClass(unionClass, memberTypeDef.getFieldName()) + : wrapperClass(unionClass, sanitizeUnknown(memberTypeDef.getFieldName())); + if (index > 0) { + initializer.add(","); + } + initializer.add("\n$T.class", wrapperClass); + } + initializer.add("$<\n}"); + return FieldSpec.builder( + ArrayTypeName.of(classType), "VARIANT_TYPES", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("$L", initializer.build()) + .build(); + } + + private static FieldSpec generateDeserializersField() { + TypeName deserializerType = ParameterizedTypeName.get( + ClassName.get(JsonDeserializer.class), WildcardTypeName.subtypeOf(Object.class)); + return FieldSpec.builder( + ArrayTypeName.of(deserializerType), "deserializers", Modifier.PRIVATE, Modifier.VOLATILE) + .build(); + } + + private static MethodSpec generateResolveMethod() { + return MethodSpec.methodBuilder("resolve") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(DeserializationContext.class, "context") + .addException(JsonMappingException.class) + .addStatement("deserializers = new $T[VARIANT_TYPES.length]", JsonDeserializer.class) + .build(); + } + + private static MethodSpec generateIsCachableMethod() { + return MethodSpec.methodBuilder("isCachable") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .returns(TypeName.BOOLEAN) + .addStatement("return true") + .build(); + } + + private static TypeSpec generateSerializer(ClassName unionClass) { + return TypeSpec.classBuilder(unionClass.nestedClass(SERIALIZER_CLASS_NAME)) + .addModifiers(Modifier.STATIC, Modifier.FINAL) + .superclass(ParameterizedTypeName.get(ClassName.get(JsonSerializer.class), unionClass)) + .addMethod(MethodSpec.methodBuilder("serialize") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(unionClass, "value") + .addParameter(JsonGenerator.class, "generator") + .addParameter(SerializerProvider.class, "serializers") + .addException(IOException.class) + .addStatement("serializers.findValueSerializer(value.getClass()).serialize(value, generator," + + " serializers)") + .build()) + .build(); + } + + private static MethodSpec generateDeserializeMethod(ClassName unionClass) { + return MethodSpec.methodBuilder("deserialize") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .returns(unionClass) + .addParameter(JsonParser.class, "parser") + .addParameter(DeserializationContext.class, "context") + .addException(IOException.class) + .beginControlFlow("if (!parser.isExpectedStartObjectToken())") + .addStatement( + "return context.reportInputMismatch($T.class, $S)", + unionClass, + "Expected a JSON object for union deserialization") + .endControlFlow() + .addStatement("$T firstToken = parser.nextToken()", JsonToken.class) + .beginControlFlow( + "if (firstToken == $T.FIELD_NAME && isTypeField(parser.currentName(), context))", + JsonToken.class) + .beginControlFlow("if (parser.nextToken() != $T.VALUE_STRING)", JsonToken.class) + .addStatement( + "return context.reportInputMismatch($T.class, $S)", + unionClass, + "Union discriminator 'type' must be a string") + .endControlFlow() + .addStatement("$T type = parser.getText()", String.class) + .addStatement("parser.nextToken()") + .addStatement("return deserializeSelected(parser, context, type)") + .endControlFlow() + .addStatement("return deserializeBuffered(parser, context)") + .build(); + } + + private static MethodSpec generateDeserializeBufferedMethod(ClassName unionClass) { + return MethodSpec.methodBuilder("deserializeBuffered") + .addModifiers(Modifier.PRIVATE) + .returns(unionClass) + .addParameter(JsonParser.class, "parser") + .addParameter(DeserializationContext.class, "context") + .addException(IOException.class) + .beginControlFlow("try ($T buffer = context.bufferForInputBuffering(parser))", TokenBuffer.class) + .addStatement("buffer.writeStartObject()") + .addStatement("$T token = parser.currentToken()", JsonToken.class) + .beginControlFlow("while (token == $T.FIELD_NAME)", JsonToken.class) + .addStatement("$T fieldName = parser.currentName()", String.class) + .addStatement("$T valueToken = parser.nextToken()", JsonToken.class) + .beginControlFlow("if (isTypeField(fieldName, context))") + .beginControlFlow("if (valueToken != $T.VALUE_STRING)", JsonToken.class) + .addStatement( + "return context.reportInputMismatch($T.class, $S)", + unionClass, + "Union discriminator 'type' must be a string") + .endControlFlow() + .addStatement("$T type = parser.getText()", String.class) + .addStatement("parser.nextToken()") + .beginControlFlow("try ($T bufferedParser = buffer.asParser(parser))", JsonParser.class) + .addStatement( + "$T combinedParser = $T.createFlattened(true, bufferedParser, parser)", + JsonParser.class, + JsonParserSequence.class) + .addStatement("combinedParser.nextToken()") + .addStatement("return deserializeSelected(combinedParser, context, type)") + .endControlFlow() + .endControlFlow() + .addStatement("buffer.writeFieldName(fieldName)") + .addStatement("buffer.copyCurrentStructure(parser)") + .addStatement("token = parser.nextToken()") + .endControlFlow() + .beginControlFlow("if (token != $T.END_OBJECT)", JsonToken.class) + .addStatement( + "return context.reportInputMismatch($T.class, $S)", + unionClass, + "Expected the end of a JSON object while deserializing a union") + .endControlFlow() + .endControlFlow() + .addStatement( + "return context.reportInputMismatch($T.class, $S)", + unionClass, + "Union discriminator 'type' is required") + .build(); + } + + private static MethodSpec generateIsTypeFieldMethod() { + return MethodSpec.methodBuilder("isTypeField") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(TypeName.BOOLEAN) + .addParameter(String.class, "fieldName") + .addParameter(DeserializationContext.class, "context") + .addStatement( + "return $S.equals(fieldName) || (context.isEnabled($T.ACCEPT_CASE_INSENSITIVE_PROPERTIES)" + + " && $S.equalsIgnoreCase(fieldName))", + "type", + MapperFeature.class, + "type") + .build(); + } + + private static MethodSpec generateDeserializeSelectedMethod( + ClassName unionClass, List memberTypeDefs, Options options) { + MethodSpec.Builder builder = MethodSpec.methodBuilder("deserializeSelected") + .addModifiers(Modifier.PRIVATE) + .returns(unionClass) + .addParameter(JsonParser.class, "parser") + .addParameter(DeserializationContext.class, "context") + .addParameter(String.class, "type") + .addException(IOException.class) + .addCode("int variantIndex = switch (type) {\n"); + for (int index = 0; index < memberTypeDefs.size(); index++) { + builder.addStatement("case $S -> $L", memberTypeDefs.get(index).getFieldName(), index); + } + builder.addStatement("default -> -1").addCode("};\n"); + builder.beginControlFlow("if (variantIndex < 0)"); + builder.addStatement("return deserializeUnknown(parser, context, type)"); + builder.endControlFlow(); + builder.addStatement("$T deserializer = deserializers[variantIndex]", JsonDeserializer.class) + .beginControlFlow("if (deserializer == null)") + .addStatement("deserializer = resolveDeserializer(context, variantIndex)") + .endControlFlow(); + if (options.sealedUnions()) { + builder.addStatement("return ($T) deserializer.deserialize(parser, context)", unionClass); + } else { + builder.addStatement( + "return new $T(($T) deserializer.deserialize(parser, context))", + unionClass, + unionClass.nestedClass("Base")); + } + return builder.build(); + } + + private static MethodSpec generateResolveDeserializerMethod() { + ParameterizedTypeName deserializerType = ParameterizedTypeName.get( + ClassName.get(JsonDeserializer.class), WildcardTypeName.subtypeOf(Object.class)); + return MethodSpec.methodBuilder("resolveDeserializer") + .addModifiers(Modifier.PRIVATE, Modifier.SYNCHRONIZED) + .returns(deserializerType) + .addParameter(DeserializationContext.class, "context") + .addParameter(TypeName.INT, "variantIndex") + .addException(JsonMappingException.class) + .addStatement("$T deserializer = deserializers[variantIndex]", deserializerType) + .beginControlFlow("if (deserializer == null)") + .addStatement("deserializer = context.findRootValueDeserializer(" + + "context.constructType(VARIANT_TYPES[variantIndex]))") + .addStatement("$T[] updated = deserializers.clone()", deserializerType) + .addStatement("updated[variantIndex] = deserializer") + .addStatement("deserializers = updated") + .endControlFlow() + .addStatement("return deserializer") + .build(); + } + + private static MethodSpec generateDeserializeUnknownMethod(ClassName unionClass, Options options) { + ParameterizedTypeName valueMapType = ParameterizedTypeName.get(Map.class, String.class, Object.class); + MethodSpec.Builder builder = MethodSpec.methodBuilder("deserializeUnknown") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(unionClass) + .addParameter(JsonParser.class, "parser") + .addParameter(DeserializationContext.class, "context") + .addParameter(String.class, "type") + .addException(IOException.class) + .addStatement("$T values = new $T<>()", valueMapType, HashMap.class) + .beginControlFlow("if (parser.currentToken() == $T.START_OBJECT)", JsonToken.class) + .addStatement("parser.nextToken()") + .endControlFlow() + .beginControlFlow("while (parser.currentToken() == $T.FIELD_NAME)", JsonToken.class) + .addStatement("$T fieldName = parser.currentName()", String.class) + .addStatement("parser.nextToken()") + .addStatement("values.put(fieldName, context.readValue(parser, $T.class))", Object.class) + .addStatement("parser.nextToken()") + .endControlFlow() + .beginControlFlow("if (parser.currentToken() != $T.END_OBJECT)", JsonToken.class) + .addStatement( + "return context.reportInputMismatch($T.class, $S)", + unionClass, + "Expected the end of a JSON object while deserializing a union") + .endControlFlow(); + if (options.sealedUnions()) { + builder.addStatement("return new $T(type, values)", unionClass.nestedClass(SEALED_UNKNOWN_VARIANT_NAME)); + } else { + builder.addStatement( + "return new $T(new $T(type, values))", + unionClass, + unionClass.nestedClass(UNKNOWN_WRAPPER_CLASS_NAME)); + } + return builder.build(); + } + /** * Generates a prototype for a visitor builder setter that can be turned into an interface method declaration or an * implementation of such interface method. The signature of the returned builder is @@ -855,34 +1129,7 @@ private static MethodSpec.Builder visitorBuilderUnknownThrowPrototype( private static TypeSpec generateBase( ClassName baseClass, ClassName visitorClass, Map memberTypes) { - ClassName unknownWrapperClass = baseClass.peerClass(UNKNOWN_WRAPPER_CLASS_NAME); - TypeSpec.Builder baseBuilder = TypeSpec.interfaceBuilder(baseClass) - .addModifiers(Modifier.PRIVATE) - .addAnnotation(AnnotationSpec.builder(JsonTypeInfo.class) - .addMember("use", "JsonTypeInfo.Id.NAME") - .addMember("include", "JsonTypeInfo.As.EXISTING_PROPERTY") - .addMember("property", "\"type\"") - .addMember("visible", "$L", true) - .addMember("defaultImpl", "$T.class", unknownWrapperClass) - .build()); - if (!memberTypes.isEmpty()) { - List subAnnotations = memberTypes.entrySet().stream() - .map(entry -> AnnotationSpec.builder(JsonSubTypes.Type.class) - .addMember( - "value", - "$T.class", - peerWrapperClass( - baseClass, - sanitizeUnknown(entry.getKey().getFieldName()))) - .build()) - .collect(Collectors.toList()); - AnnotationSpec.Builder annotationBuilder = AnnotationSpec.builder(JsonSubTypes.class); - subAnnotations.forEach(subAnnotation -> annotationBuilder.addMember("value", "$L", subAnnotation)); - baseBuilder.addAnnotation(annotationBuilder.build()); - } - baseBuilder.addAnnotation(AnnotationSpec.builder(JsonIgnoreProperties.class) - .addMember("ignoreUnknown", "$L", true) - .build()); + TypeSpec.Builder baseBuilder = TypeSpec.interfaceBuilder(baseClass).addModifiers(Modifier.PRIVATE); ParameterizedTypeName parameterizedVisitorClass = ParameterizedTypeName.get(visitorClass, TYPE_VARIABLE); ParameterSpec visitor = ParameterSpec.builder(parameterizedVisitorClass, "visitor").build(); @@ -924,6 +1171,7 @@ private static List generateWrapperClasses( .addAnnotation(AnnotationSpec.builder(JsonTypeName.class) .addMember("value", "$S", memberTypeDef.getFieldName()) .build()) + .addAnnotation(ignoreUnknownAnnotation()) .addFields(fields) .addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE) @@ -959,6 +1207,13 @@ private static List generateWrapperClasses( .returns(memberType) .build()); + if (options.sealedUnions()) { + // Prevent the custom union serializer and deserializer on the sealed base class from being + // inherited by concrete variants when delegating directly to a known wrapper. + typeBuilder.addAnnotation(JsonDeserialize.class); + typeBuilder.addAnnotation(JsonSerialize.class); + } + if (!options.sealedUnions() || (options.sealedUnions() && options.sealedUnionVisitors())) { typeBuilder.addMethod(createWrapperAcceptMethod( visitorClass, @@ -1103,6 +1358,11 @@ private static TypeSpec generateUnknownWrapper(ClassName baseClass, ClassName vi .addStatement("$L.put(key, val)", VALUE_FIELD_NAME) .build()); + if (options.sealedUnions()) { + typeBuilder.addAnnotation(JsonDeserialize.class); + typeBuilder.addAnnotation(JsonSerialize.class); + } + if (!options.sealedUnions() || (options.sealedUnions() && options.sealedUnionVisitors())) { typeBuilder.addMethod(createWrapperAcceptMethod( visitorClass, VISIT_UNKNOWN_METHOD_NAME, typeParameter.name(), false, options)); diff --git a/conjure-java-server-verifier/build.gradle b/conjure-java-server-verifier/build.gradle index 0d09b6702..9d24ac07a 100644 --- a/conjure-java-server-verifier/build.gradle +++ b/conjure-java-server-verifier/build.gradle @@ -72,6 +72,7 @@ project('verification-client-api') { implementation project(':conjure-java-client-verifier:verification-server-api') implementation 'com.fasterxml.jackson.core:jackson-annotations' + implementation 'com.fasterxml.jackson.core:jackson-core' implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.google.code.findbugs:jsr305' implementation 'com.google.errorprone:error_prone_annotations' @@ -90,4 +91,3 @@ tasks.withType(JavaCompile).matching { it.name == "compileTestJava" }.configureE } } - From 38e2d94d4362137031257b7001e891e2cff41d4e Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Fri, 21 Aug 2026 16:46:40 +0000 Subject: [PATCH 3/9] Regenerate integration input sources --- .../product/AliasAsMapKeyExample.java | 14 +- .../com/palantir/product/AnyMapExample.java | 2 +- .../product/EmptyUnionTypeExample.java | 144 ++++- .../product/ExternalLongUnionExample.java | 151 ++++- .../palantir/product/LargeUnionExample.java | 550 ++++++++++++++---- .../palantir/product/ManyFieldExample.java | 2 +- .../com/palantir/product/MapExample.java | 6 +- .../com/palantir/product/SingleUnion.java | 145 ++++- .../com/palantir/product/Union.java | 152 ++++- .../palantir/product/UnionTypeExample.java | 221 ++++++- .../product/UnionWithUnknownString.java | 148 ++++- .../palantir/product/StrictFourFields.java | 2 +- ...rictMultipleDeprecatedAndUnsafeFields.java | 2 +- .../ExampleDefensiveCollectionListsUnion.java | 161 ++++- .../ExampleDefensiveCollectionMapsUnion.java | 154 ++++- .../ExampleDefensiveCollectionSetsUnion.java | 151 ++++- .../ExampleDefensiveCollectionListsUnion.java | 161 ++++- .../ExampleDefensiveCollectionMapsUnion.java | 154 ++++- .../ExampleDefensiveCollectionSetsUnion.java | 151 ++++- .../com/palantir/product/AnyExample.java | 2 +- .../palantir/product/CollectionExample.java | 2 +- .../com/palantir/product/ComplexExample.java | 2 +- .../com/palantir/product/EmptyUnion.java | 156 ++++- .../product/NestedCollectionExample.java | 4 +- .../com/palantir/product/SimpleUnion.java | 173 +++++- .../com/palantir/product/UnionExample.java | 185 +++++- .../com/palantir/product/AnyExample.java | 2 +- .../palantir/product/CollectionExample.java | 2 +- .../com/palantir/product/ComplexExample.java | 2 +- .../com/palantir/product/EmptyUnion.java | 141 ++++- .../product/NestedCollectionExample.java | 4 +- .../com/palantir/product/UnionExample.java | 165 +++++- .../com/palantir/product/AnyExample.java | 2 +- .../palantir/product/CollectionExample.java | 2 +- .../com/palantir/product/ComplexExample.java | 2 +- .../com/palantir/product/EmptyUnion.java | 141 ++++- .../product/NestedCollectionExample.java | 4 +- .../com/palantir/product/SimpleUnion.java | 154 ++++- .../com/palantir/product/UnionExample.java | 165 +++++- .../product/datasets/BackingFileSystem.java | 2 +- .../product/CollectionsTestObject.java | 2 +- .../product/external/UnionWithExternal.java | 146 ++++- .../product/datasets/BackingFileSystem.java | 2 +- .../com/palantir/product/SimpleUnion.java | 154 ++++- .../product/datasets/BackingFileSystem.java | 2 +- .../com/palantir/product/CamelCaseUnion.java | 162 +++++- .../com/palantir/product/EmptyUnion.java | 156 ++++- .../palantir/product/NestedEmptyUnion.java | 162 +++++- .../com/palantir/product/SimpleUnion.java | 173 +++++- .../palantir/product/UnionReservedNames.java | 250 +++++++- .../product/datasets/BackingFileSystem.java | 2 +- .../product/MultipleFieldsOneFinalStage.java | 2 +- .../product/MultipleFieldsOnlyFinalStage.java | 4 +- .../product/MultipleOrderedStages.java | 2 +- .../palantir/product/StrictFourFields.java | 2 +- ...rictMultipleDeprecatedAndUnsafeFields.java | 2 +- .../product/AliasAsMapKeyExample.java | 14 +- .../com/palantir/product/AnyMapExample.java | 2 +- .../product/EmptyUnionTypeExample.java | 144 ++++- .../product/ExternalLongUnionExample.java | 151 ++++- .../palantir/product/LargeUnionExample.java | 550 ++++++++++++++---- .../palantir/product/ManyFieldExample.java | 2 +- .../com/palantir/product/MapExample.java | 6 +- .../com/palantir/product/SingleUnion.java | 145 ++++- .../template/com/palantir/product/Union.java | 152 ++++- .../palantir/product/UnionTypeExample.java | 221 ++++++- .../product/UnionWithUnknownString.java | 148 ++++- .../com/palantir/product/SimpleUnion.java | 154 ++++- 68 files changed, 5981 insertions(+), 714 deletions(-) diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/AliasAsMapKeyExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/AliasAsMapKeyExample.java index 104e651f3..4ffaa6ae9 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/AliasAsMapKeyExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/AliasAsMapKeyExample.java @@ -178,18 +178,25 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; + @JsonSetter(value = "strings", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map strings = new LinkedHashMap<>(); + @JsonSetter(value = "rids", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map rids = new LinkedHashMap<>(); + @JsonSetter(value = "bearertokens", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map bearertokens = new LinkedHashMap<>(); + @JsonSetter(value = "integers", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map integers = new LinkedHashMap<>(); + @JsonSetter(value = "safelongs", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map safelongs = new LinkedHashMap<>(); + @JsonSetter(value = "datetimes", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map datetimes = new LinkedHashMap<>(); + @JsonSetter(value = "uuids", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map uuids = new LinkedHashMap<>(); private Builder() {} @@ -206,7 +213,6 @@ public Builder from(AliasAsMapKeyExample other) { return this; } - @JsonSetter(value = "strings", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder strings(@Nonnull Map strings) { checkNotBuilt(); this.strings = new LinkedHashMap<>(Preconditions.checkNotNull(strings, "strings cannot be null")); @@ -225,7 +231,6 @@ public Builder strings(StringAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "rids", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder rids(@Nonnull Map rids) { checkNotBuilt(); this.rids = new LinkedHashMap<>(Preconditions.checkNotNull(rids, "rids cannot be null")); @@ -244,7 +249,6 @@ public Builder rids(RidAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "bearertokens", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder bearertokens(@Nonnull Map bearertokens) { checkNotBuilt(); this.bearertokens = @@ -264,7 +268,6 @@ public Builder bearertokens(BearerTokenAliasExample key, ManyFieldExample value) return this; } - @JsonSetter(value = "integers", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder integers(@Nonnull Map integers) { checkNotBuilt(); this.integers = new LinkedHashMap<>(Preconditions.checkNotNull(integers, "integers cannot be null")); @@ -283,7 +286,6 @@ public Builder integers(IntegerAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "safelongs", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder safelongs(@Nonnull Map safelongs) { checkNotBuilt(); this.safelongs = new LinkedHashMap<>(Preconditions.checkNotNull(safelongs, "safelongs cannot be null")); @@ -302,7 +304,6 @@ public Builder safelongs(SafeLongAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "datetimes", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder datetimes(@Nonnull Map datetimes) { checkNotBuilt(); this.datetimes = new LinkedHashMap<>(Preconditions.checkNotNull(datetimes, "datetimes cannot be null")); @@ -321,7 +322,6 @@ public Builder datetimes(DateTimeAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "uuids", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder uuids(@Nonnull Map uuids) { checkNotBuilt(); this.uuids = new LinkedHashMap<>(Preconditions.checkNotNull(uuids, "uuids cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/AnyMapExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/AnyMapExample.java index 7c621d7d5..41d11b4a6 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/AnyMapExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/AnyMapExample.java @@ -95,6 +95,7 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; + @JsonSetter(value = "items", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map items = new LinkedHashMap<>(); private Builder() {} @@ -105,7 +106,6 @@ public Builder from(AnyMapExample other) { return this; } - @JsonSetter(value = "items", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder items(@Nonnull Map items) { checkNotBuilt(); this.items = new LinkedHashMap<>(Preconditions.checkNotNull(items, "items cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java index a1ec49933..4b04044ac 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java @@ -3,14 +3,23 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -21,6 +30,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = EmptyUnionTypeExample.Deserializer.class) public final class EmptyUnionTypeExample { private final Base value; @@ -123,13 +133,6 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @@ -193,4 +196,127 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public EmptyUnionTypeExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private EmptyUnionTypeExample deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(EmptyUnionTypeExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private EmptyUnionTypeExample deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new EmptyUnionTypeExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static EmptyUnionTypeExample deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + } + return new EmptyUnionTypeExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java index 8d99c73b4..c0759009e 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java @@ -6,14 +6,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,6 +34,7 @@ /** A union of a safe long. */ @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = ExternalLongUnionExample.Deserializer.class) public final class ExternalLongUnionExample { private final Base value; @@ -157,19 +167,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes(@JsonSubTypes.Type(SafeLongWrapper.class)) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("safeLong") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SafeLongWrapper implements Base { private final long value; @@ -273,4 +276,132 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer + implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {SafeLongWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public ExternalLongUnionExample deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private ExternalLongUnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch( + ExternalLongUnionExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private ExternalLongUnionExample deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "safeLong" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new ExternalLongUnionExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static ExternalLongUnionExample deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, + "Expected the end of a JSON object while deserializing a union"); + } + return new ExternalLongUnionExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java index 2f3db496f..926bdd446 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java @@ -6,13 +6,22 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -21,6 +30,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = LargeUnionExample.Deserializer.class) public final class LargeUnionExample { private final Base value; @@ -977,121 +987,12 @@ public interface Visitor { T visitUnknown(@Safe String unknownType, Object unknownValue); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(U0Wrapper.class), - @JsonSubTypes.Type(U1Wrapper.class), - @JsonSubTypes.Type(U2Wrapper.class), - @JsonSubTypes.Type(U3Wrapper.class), - @JsonSubTypes.Type(U4Wrapper.class), - @JsonSubTypes.Type(U5Wrapper.class), - @JsonSubTypes.Type(U6Wrapper.class), - @JsonSubTypes.Type(U7Wrapper.class), - @JsonSubTypes.Type(U8Wrapper.class), - @JsonSubTypes.Type(U9Wrapper.class), - @JsonSubTypes.Type(U10Wrapper.class), - @JsonSubTypes.Type(U11Wrapper.class), - @JsonSubTypes.Type(U12Wrapper.class), - @JsonSubTypes.Type(U13Wrapper.class), - @JsonSubTypes.Type(U14Wrapper.class), - @JsonSubTypes.Type(U15Wrapper.class), - @JsonSubTypes.Type(U16Wrapper.class), - @JsonSubTypes.Type(U17Wrapper.class), - @JsonSubTypes.Type(U18Wrapper.class), - @JsonSubTypes.Type(U19Wrapper.class), - @JsonSubTypes.Type(U20Wrapper.class), - @JsonSubTypes.Type(U21Wrapper.class), - @JsonSubTypes.Type(U22Wrapper.class), - @JsonSubTypes.Type(U23Wrapper.class), - @JsonSubTypes.Type(U24Wrapper.class), - @JsonSubTypes.Type(U25Wrapper.class), - @JsonSubTypes.Type(U26Wrapper.class), - @JsonSubTypes.Type(U27Wrapper.class), - @JsonSubTypes.Type(U28Wrapper.class), - @JsonSubTypes.Type(U29Wrapper.class), - @JsonSubTypes.Type(U30Wrapper.class), - @JsonSubTypes.Type(U31Wrapper.class), - @JsonSubTypes.Type(U32Wrapper.class), - @JsonSubTypes.Type(U33Wrapper.class), - @JsonSubTypes.Type(U34Wrapper.class), - @JsonSubTypes.Type(U35Wrapper.class), - @JsonSubTypes.Type(U36Wrapper.class), - @JsonSubTypes.Type(U37Wrapper.class), - @JsonSubTypes.Type(U38Wrapper.class), - @JsonSubTypes.Type(U39Wrapper.class), - @JsonSubTypes.Type(U40Wrapper.class), - @JsonSubTypes.Type(U41Wrapper.class), - @JsonSubTypes.Type(U42Wrapper.class), - @JsonSubTypes.Type(U43Wrapper.class), - @JsonSubTypes.Type(U44Wrapper.class), - @JsonSubTypes.Type(U45Wrapper.class), - @JsonSubTypes.Type(U46Wrapper.class), - @JsonSubTypes.Type(U47Wrapper.class), - @JsonSubTypes.Type(U48Wrapper.class), - @JsonSubTypes.Type(U49Wrapper.class), - @JsonSubTypes.Type(U50Wrapper.class), - @JsonSubTypes.Type(U51Wrapper.class), - @JsonSubTypes.Type(U52Wrapper.class), - @JsonSubTypes.Type(U53Wrapper.class), - @JsonSubTypes.Type(U54Wrapper.class), - @JsonSubTypes.Type(U55Wrapper.class), - @JsonSubTypes.Type(U56Wrapper.class), - @JsonSubTypes.Type(U57Wrapper.class), - @JsonSubTypes.Type(U58Wrapper.class), - @JsonSubTypes.Type(U59Wrapper.class), - @JsonSubTypes.Type(U60Wrapper.class), - @JsonSubTypes.Type(U61Wrapper.class), - @JsonSubTypes.Type(U62Wrapper.class), - @JsonSubTypes.Type(U63Wrapper.class), - @JsonSubTypes.Type(U64Wrapper.class), - @JsonSubTypes.Type(U65Wrapper.class), - @JsonSubTypes.Type(U66Wrapper.class), - @JsonSubTypes.Type(U67Wrapper.class), - @JsonSubTypes.Type(U68Wrapper.class), - @JsonSubTypes.Type(U69Wrapper.class), - @JsonSubTypes.Type(U70Wrapper.class), - @JsonSubTypes.Type(U71Wrapper.class), - @JsonSubTypes.Type(U72Wrapper.class), - @JsonSubTypes.Type(U73Wrapper.class), - @JsonSubTypes.Type(U74Wrapper.class), - @JsonSubTypes.Type(U75Wrapper.class), - @JsonSubTypes.Type(U76Wrapper.class), - @JsonSubTypes.Type(U77Wrapper.class), - @JsonSubTypes.Type(U78Wrapper.class), - @JsonSubTypes.Type(U79Wrapper.class), - @JsonSubTypes.Type(U80Wrapper.class), - @JsonSubTypes.Type(U81Wrapper.class), - @JsonSubTypes.Type(U82Wrapper.class), - @JsonSubTypes.Type(U83Wrapper.class), - @JsonSubTypes.Type(U84Wrapper.class), - @JsonSubTypes.Type(U85Wrapper.class), - @JsonSubTypes.Type(U86Wrapper.class), - @JsonSubTypes.Type(U87Wrapper.class), - @JsonSubTypes.Type(U88Wrapper.class), - @JsonSubTypes.Type(U89Wrapper.class), - @JsonSubTypes.Type(U90Wrapper.class), - @JsonSubTypes.Type(U91Wrapper.class), - @JsonSubTypes.Type(U92Wrapper.class), - @JsonSubTypes.Type(U93Wrapper.class), - @JsonSubTypes.Type(U94Wrapper.class), - @JsonSubTypes.Type(U95Wrapper.class), - @JsonSubTypes.Type(U96Wrapper.class), - @JsonSubTypes.Type(U97Wrapper.class), - @JsonSubTypes.Type(U98Wrapper.class), - @JsonSubTypes.Type(U99Wrapper.class), - @JsonSubTypes.Type(U100Wrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("u0") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U0Wrapper implements Base { private final String value; @@ -1137,6 +1038,7 @@ public String toString() { } @JsonTypeName("u1") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U1Wrapper implements Base { private final String value; @@ -1182,6 +1084,7 @@ public String toString() { } @JsonTypeName("u2") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U2Wrapper implements Base { private final String value; @@ -1227,6 +1130,7 @@ public String toString() { } @JsonTypeName("u3") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U3Wrapper implements Base { private final String value; @@ -1272,6 +1176,7 @@ public String toString() { } @JsonTypeName("u4") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U4Wrapper implements Base { private final String value; @@ -1317,6 +1222,7 @@ public String toString() { } @JsonTypeName("u5") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U5Wrapper implements Base { private final String value; @@ -1362,6 +1268,7 @@ public String toString() { } @JsonTypeName("u6") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U6Wrapper implements Base { private final String value; @@ -1407,6 +1314,7 @@ public String toString() { } @JsonTypeName("u7") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U7Wrapper implements Base { private final String value; @@ -1452,6 +1360,7 @@ public String toString() { } @JsonTypeName("u8") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U8Wrapper implements Base { private final String value; @@ -1497,6 +1406,7 @@ public String toString() { } @JsonTypeName("u9") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U9Wrapper implements Base { private final String value; @@ -1542,6 +1452,7 @@ public String toString() { } @JsonTypeName("u10") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U10Wrapper implements Base { private final String value; @@ -1587,6 +1498,7 @@ public String toString() { } @JsonTypeName("u11") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U11Wrapper implements Base { private final String value; @@ -1632,6 +1544,7 @@ public String toString() { } @JsonTypeName("u12") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U12Wrapper implements Base { private final String value; @@ -1677,6 +1590,7 @@ public String toString() { } @JsonTypeName("u13") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U13Wrapper implements Base { private final String value; @@ -1722,6 +1636,7 @@ public String toString() { } @JsonTypeName("u14") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U14Wrapper implements Base { private final String value; @@ -1767,6 +1682,7 @@ public String toString() { } @JsonTypeName("u15") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U15Wrapper implements Base { private final String value; @@ -1812,6 +1728,7 @@ public String toString() { } @JsonTypeName("u16") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U16Wrapper implements Base { private final String value; @@ -1857,6 +1774,7 @@ public String toString() { } @JsonTypeName("u17") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U17Wrapper implements Base { private final String value; @@ -1902,6 +1820,7 @@ public String toString() { } @JsonTypeName("u18") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U18Wrapper implements Base { private final String value; @@ -1947,6 +1866,7 @@ public String toString() { } @JsonTypeName("u19") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U19Wrapper implements Base { private final String value; @@ -1992,6 +1912,7 @@ public String toString() { } @JsonTypeName("u20") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U20Wrapper implements Base { private final String value; @@ -2037,6 +1958,7 @@ public String toString() { } @JsonTypeName("u21") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U21Wrapper implements Base { private final String value; @@ -2082,6 +2004,7 @@ public String toString() { } @JsonTypeName("u22") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U22Wrapper implements Base { private final String value; @@ -2127,6 +2050,7 @@ public String toString() { } @JsonTypeName("u23") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U23Wrapper implements Base { private final String value; @@ -2172,6 +2096,7 @@ public String toString() { } @JsonTypeName("u24") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U24Wrapper implements Base { private final String value; @@ -2217,6 +2142,7 @@ public String toString() { } @JsonTypeName("u25") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U25Wrapper implements Base { private final String value; @@ -2262,6 +2188,7 @@ public String toString() { } @JsonTypeName("u26") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U26Wrapper implements Base { private final String value; @@ -2307,6 +2234,7 @@ public String toString() { } @JsonTypeName("u27") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U27Wrapper implements Base { private final String value; @@ -2352,6 +2280,7 @@ public String toString() { } @JsonTypeName("u28") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U28Wrapper implements Base { private final String value; @@ -2397,6 +2326,7 @@ public String toString() { } @JsonTypeName("u29") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U29Wrapper implements Base { private final String value; @@ -2442,6 +2372,7 @@ public String toString() { } @JsonTypeName("u30") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U30Wrapper implements Base { private final String value; @@ -2487,6 +2418,7 @@ public String toString() { } @JsonTypeName("u31") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U31Wrapper implements Base { private final String value; @@ -2532,6 +2464,7 @@ public String toString() { } @JsonTypeName("u32") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U32Wrapper implements Base { private final String value; @@ -2577,6 +2510,7 @@ public String toString() { } @JsonTypeName("u33") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U33Wrapper implements Base { private final String value; @@ -2622,6 +2556,7 @@ public String toString() { } @JsonTypeName("u34") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U34Wrapper implements Base { private final String value; @@ -2667,6 +2602,7 @@ public String toString() { } @JsonTypeName("u35") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U35Wrapper implements Base { private final String value; @@ -2712,6 +2648,7 @@ public String toString() { } @JsonTypeName("u36") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U36Wrapper implements Base { private final String value; @@ -2757,6 +2694,7 @@ public String toString() { } @JsonTypeName("u37") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U37Wrapper implements Base { private final String value; @@ -2802,6 +2740,7 @@ public String toString() { } @JsonTypeName("u38") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U38Wrapper implements Base { private final String value; @@ -2847,6 +2786,7 @@ public String toString() { } @JsonTypeName("u39") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U39Wrapper implements Base { private final String value; @@ -2892,6 +2832,7 @@ public String toString() { } @JsonTypeName("u40") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U40Wrapper implements Base { private final String value; @@ -2937,6 +2878,7 @@ public String toString() { } @JsonTypeName("u41") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U41Wrapper implements Base { private final String value; @@ -2982,6 +2924,7 @@ public String toString() { } @JsonTypeName("u42") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U42Wrapper implements Base { private final String value; @@ -3027,6 +2970,7 @@ public String toString() { } @JsonTypeName("u43") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U43Wrapper implements Base { private final String value; @@ -3072,6 +3016,7 @@ public String toString() { } @JsonTypeName("u44") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U44Wrapper implements Base { private final String value; @@ -3117,6 +3062,7 @@ public String toString() { } @JsonTypeName("u45") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U45Wrapper implements Base { private final String value; @@ -3162,6 +3108,7 @@ public String toString() { } @JsonTypeName("u46") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U46Wrapper implements Base { private final String value; @@ -3207,6 +3154,7 @@ public String toString() { } @JsonTypeName("u47") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U47Wrapper implements Base { private final String value; @@ -3252,6 +3200,7 @@ public String toString() { } @JsonTypeName("u48") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U48Wrapper implements Base { private final String value; @@ -3297,6 +3246,7 @@ public String toString() { } @JsonTypeName("u49") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U49Wrapper implements Base { private final String value; @@ -3342,6 +3292,7 @@ public String toString() { } @JsonTypeName("u50") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U50Wrapper implements Base { private final String value; @@ -3387,6 +3338,7 @@ public String toString() { } @JsonTypeName("u51") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U51Wrapper implements Base { private final String value; @@ -3432,6 +3384,7 @@ public String toString() { } @JsonTypeName("u52") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U52Wrapper implements Base { private final String value; @@ -3477,6 +3430,7 @@ public String toString() { } @JsonTypeName("u53") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U53Wrapper implements Base { private final String value; @@ -3522,6 +3476,7 @@ public String toString() { } @JsonTypeName("u54") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U54Wrapper implements Base { private final String value; @@ -3567,6 +3522,7 @@ public String toString() { } @JsonTypeName("u55") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U55Wrapper implements Base { private final String value; @@ -3612,6 +3568,7 @@ public String toString() { } @JsonTypeName("u56") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U56Wrapper implements Base { private final String value; @@ -3657,6 +3614,7 @@ public String toString() { } @JsonTypeName("u57") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U57Wrapper implements Base { private final String value; @@ -3702,6 +3660,7 @@ public String toString() { } @JsonTypeName("u58") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U58Wrapper implements Base { private final String value; @@ -3747,6 +3706,7 @@ public String toString() { } @JsonTypeName("u59") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U59Wrapper implements Base { private final String value; @@ -3792,6 +3752,7 @@ public String toString() { } @JsonTypeName("u60") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U60Wrapper implements Base { private final String value; @@ -3837,6 +3798,7 @@ public String toString() { } @JsonTypeName("u61") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U61Wrapper implements Base { private final String value; @@ -3882,6 +3844,7 @@ public String toString() { } @JsonTypeName("u62") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U62Wrapper implements Base { private final String value; @@ -3927,6 +3890,7 @@ public String toString() { } @JsonTypeName("u63") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U63Wrapper implements Base { private final String value; @@ -3972,6 +3936,7 @@ public String toString() { } @JsonTypeName("u64") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U64Wrapper implements Base { private final String value; @@ -4017,6 +3982,7 @@ public String toString() { } @JsonTypeName("u65") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U65Wrapper implements Base { private final String value; @@ -4062,6 +4028,7 @@ public String toString() { } @JsonTypeName("u66") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U66Wrapper implements Base { private final String value; @@ -4107,6 +4074,7 @@ public String toString() { } @JsonTypeName("u67") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U67Wrapper implements Base { private final String value; @@ -4152,6 +4120,7 @@ public String toString() { } @JsonTypeName("u68") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U68Wrapper implements Base { private final String value; @@ -4197,6 +4166,7 @@ public String toString() { } @JsonTypeName("u69") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U69Wrapper implements Base { private final String value; @@ -4242,6 +4212,7 @@ public String toString() { } @JsonTypeName("u70") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U70Wrapper implements Base { private final String value; @@ -4287,6 +4258,7 @@ public String toString() { } @JsonTypeName("u71") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U71Wrapper implements Base { private final String value; @@ -4332,6 +4304,7 @@ public String toString() { } @JsonTypeName("u72") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U72Wrapper implements Base { private final String value; @@ -4377,6 +4350,7 @@ public String toString() { } @JsonTypeName("u73") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U73Wrapper implements Base { private final String value; @@ -4422,6 +4396,7 @@ public String toString() { } @JsonTypeName("u74") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U74Wrapper implements Base { private final String value; @@ -4467,6 +4442,7 @@ public String toString() { } @JsonTypeName("u75") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U75Wrapper implements Base { private final String value; @@ -4512,6 +4488,7 @@ public String toString() { } @JsonTypeName("u76") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U76Wrapper implements Base { private final String value; @@ -4557,6 +4534,7 @@ public String toString() { } @JsonTypeName("u77") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U77Wrapper implements Base { private final String value; @@ -4602,6 +4580,7 @@ public String toString() { } @JsonTypeName("u78") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U78Wrapper implements Base { private final String value; @@ -4647,6 +4626,7 @@ public String toString() { } @JsonTypeName("u79") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U79Wrapper implements Base { private final String value; @@ -4692,6 +4672,7 @@ public String toString() { } @JsonTypeName("u80") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U80Wrapper implements Base { private final String value; @@ -4737,6 +4718,7 @@ public String toString() { } @JsonTypeName("u81") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U81Wrapper implements Base { private final String value; @@ -4782,6 +4764,7 @@ public String toString() { } @JsonTypeName("u82") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U82Wrapper implements Base { private final String value; @@ -4827,6 +4810,7 @@ public String toString() { } @JsonTypeName("u83") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U83Wrapper implements Base { private final String value; @@ -4872,6 +4856,7 @@ public String toString() { } @JsonTypeName("u84") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U84Wrapper implements Base { private final String value; @@ -4917,6 +4902,7 @@ public String toString() { } @JsonTypeName("u85") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U85Wrapper implements Base { private final String value; @@ -4962,6 +4948,7 @@ public String toString() { } @JsonTypeName("u86") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U86Wrapper implements Base { private final String value; @@ -5007,6 +4994,7 @@ public String toString() { } @JsonTypeName("u87") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U87Wrapper implements Base { private final String value; @@ -5052,6 +5040,7 @@ public String toString() { } @JsonTypeName("u88") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U88Wrapper implements Base { private final String value; @@ -5097,6 +5086,7 @@ public String toString() { } @JsonTypeName("u89") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U89Wrapper implements Base { private final String value; @@ -5142,6 +5132,7 @@ public String toString() { } @JsonTypeName("u90") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U90Wrapper implements Base { private final String value; @@ -5187,6 +5178,7 @@ public String toString() { } @JsonTypeName("u91") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U91Wrapper implements Base { private final String value; @@ -5232,6 +5224,7 @@ public String toString() { } @JsonTypeName("u92") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U92Wrapper implements Base { private final String value; @@ -5277,6 +5270,7 @@ public String toString() { } @JsonTypeName("u93") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U93Wrapper implements Base { private final String value; @@ -5322,6 +5316,7 @@ public String toString() { } @JsonTypeName("u94") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U94Wrapper implements Base { private final String value; @@ -5367,6 +5362,7 @@ public String toString() { } @JsonTypeName("u95") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U95Wrapper implements Base { private final String value; @@ -5412,6 +5408,7 @@ public String toString() { } @JsonTypeName("u96") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U96Wrapper implements Base { private final String value; @@ -5457,6 +5454,7 @@ public String toString() { } @JsonTypeName("u97") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U97Wrapper implements Base { private final String value; @@ -5502,6 +5500,7 @@ public String toString() { } @JsonTypeName("u98") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U98Wrapper implements Base { private final String value; @@ -5547,6 +5546,7 @@ public String toString() { } @JsonTypeName("u99") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U99Wrapper implements Base { private final String value; @@ -5592,6 +5592,7 @@ public String toString() { } @JsonTypeName("u100") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U100Wrapper implements Base { private final String value; @@ -5695,4 +5696,329 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] { + U0Wrapper.class, + U1Wrapper.class, + U2Wrapper.class, + U3Wrapper.class, + U4Wrapper.class, + U5Wrapper.class, + U6Wrapper.class, + U7Wrapper.class, + U8Wrapper.class, + U9Wrapper.class, + U10Wrapper.class, + U11Wrapper.class, + U12Wrapper.class, + U13Wrapper.class, + U14Wrapper.class, + U15Wrapper.class, + U16Wrapper.class, + U17Wrapper.class, + U18Wrapper.class, + U19Wrapper.class, + U20Wrapper.class, + U21Wrapper.class, + U22Wrapper.class, + U23Wrapper.class, + U24Wrapper.class, + U25Wrapper.class, + U26Wrapper.class, + U27Wrapper.class, + U28Wrapper.class, + U29Wrapper.class, + U30Wrapper.class, + U31Wrapper.class, + U32Wrapper.class, + U33Wrapper.class, + U34Wrapper.class, + U35Wrapper.class, + U36Wrapper.class, + U37Wrapper.class, + U38Wrapper.class, + U39Wrapper.class, + U40Wrapper.class, + U41Wrapper.class, + U42Wrapper.class, + U43Wrapper.class, + U44Wrapper.class, + U45Wrapper.class, + U46Wrapper.class, + U47Wrapper.class, + U48Wrapper.class, + U49Wrapper.class, + U50Wrapper.class, + U51Wrapper.class, + U52Wrapper.class, + U53Wrapper.class, + U54Wrapper.class, + U55Wrapper.class, + U56Wrapper.class, + U57Wrapper.class, + U58Wrapper.class, + U59Wrapper.class, + U60Wrapper.class, + U61Wrapper.class, + U62Wrapper.class, + U63Wrapper.class, + U64Wrapper.class, + U65Wrapper.class, + U66Wrapper.class, + U67Wrapper.class, + U68Wrapper.class, + U69Wrapper.class, + U70Wrapper.class, + U71Wrapper.class, + U72Wrapper.class, + U73Wrapper.class, + U74Wrapper.class, + U75Wrapper.class, + U76Wrapper.class, + U77Wrapper.class, + U78Wrapper.class, + U79Wrapper.class, + U80Wrapper.class, + U81Wrapper.class, + U82Wrapper.class, + U83Wrapper.class, + U84Wrapper.class, + U85Wrapper.class, + U86Wrapper.class, + U87Wrapper.class, + U88Wrapper.class, + U89Wrapper.class, + U90Wrapper.class, + U91Wrapper.class, + U92Wrapper.class, + U93Wrapper.class, + U94Wrapper.class, + U95Wrapper.class, + U96Wrapper.class, + U97Wrapper.class, + U98Wrapper.class, + U99Wrapper.class, + U100Wrapper.class + }; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public LargeUnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + LargeUnionExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + LargeUnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private LargeUnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + LargeUnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + LargeUnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(LargeUnionExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private LargeUnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "u0" -> 0; + case "u1" -> 1; + case "u2" -> 2; + case "u3" -> 3; + case "u4" -> 4; + case "u5" -> 5; + case "u6" -> 6; + case "u7" -> 7; + case "u8" -> 8; + case "u9" -> 9; + case "u10" -> 10; + case "u11" -> 11; + case "u12" -> 12; + case "u13" -> 13; + case "u14" -> 14; + case "u15" -> 15; + case "u16" -> 16; + case "u17" -> 17; + case "u18" -> 18; + case "u19" -> 19; + case "u20" -> 20; + case "u21" -> 21; + case "u22" -> 22; + case "u23" -> 23; + case "u24" -> 24; + case "u25" -> 25; + case "u26" -> 26; + case "u27" -> 27; + case "u28" -> 28; + case "u29" -> 29; + case "u30" -> 30; + case "u31" -> 31; + case "u32" -> 32; + case "u33" -> 33; + case "u34" -> 34; + case "u35" -> 35; + case "u36" -> 36; + case "u37" -> 37; + case "u38" -> 38; + case "u39" -> 39; + case "u40" -> 40; + case "u41" -> 41; + case "u42" -> 42; + case "u43" -> 43; + case "u44" -> 44; + case "u45" -> 45; + case "u46" -> 46; + case "u47" -> 47; + case "u48" -> 48; + case "u49" -> 49; + case "u50" -> 50; + case "u51" -> 51; + case "u52" -> 52; + case "u53" -> 53; + case "u54" -> 54; + case "u55" -> 55; + case "u56" -> 56; + case "u57" -> 57; + case "u58" -> 58; + case "u59" -> 59; + case "u60" -> 60; + case "u61" -> 61; + case "u62" -> 62; + case "u63" -> 63; + case "u64" -> 64; + case "u65" -> 65; + case "u66" -> 66; + case "u67" -> 67; + case "u68" -> 68; + case "u69" -> 69; + case "u70" -> 70; + case "u71" -> 71; + case "u72" -> 72; + case "u73" -> 73; + case "u74" -> 74; + case "u75" -> 75; + case "u76" -> 76; + case "u77" -> 77; + case "u78" -> 78; + case "u79" -> 79; + case "u80" -> 80; + case "u81" -> 81; + case "u82" -> 82; + case "u83" -> 83; + case "u84" -> 84; + case "u85" -> 85; + case "u86" -> 86; + case "u87" -> 87; + case "u88" -> 88; + case "u89" -> 89; + case "u90" -> 90; + case "u91" -> 91; + case "u92" -> 92; + case "u93" -> 93; + case "u94" -> 94; + case "u95" -> 95; + case "u96" -> 96; + case "u97" -> 97; + case "u98" -> 98; + case "u99" -> 99; + case "u100" -> 100; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new LargeUnionExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static LargeUnionExample deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + LargeUnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + return new LargeUnionExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ManyFieldExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ManyFieldExample.java index f55f549c3..f5222f968 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ManyFieldExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ManyFieldExample.java @@ -220,6 +220,7 @@ public static final class Builder { private Set set = ConjureCollections.newNonNullSet(); + @JsonSetter(value = "map", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map map = new LinkedHashMap<>(); private StringAliasExample alias; @@ -343,7 +344,6 @@ public Builder set(String set) { /** @deprecated deprecation documentation. */ @Deprecated - @JsonSetter(value = "map", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder map(@Nonnull Map map) { checkNotBuilt(); this.map = new LinkedHashMap<>(Preconditions.checkNotNull(map, "map cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/MapExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/MapExample.java index 5a88a5b57..b33f597fd 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/MapExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/MapExample.java @@ -134,10 +134,13 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; + @JsonSetter(value = "items", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map items = new LinkedHashMap<>(); + @JsonSetter(value = "optionalItems", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) private Map> optionalItems = new LinkedHashMap<>(); + @JsonSetter(value = "aliasOptionalItems", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) private Map aliasOptionalItems = new LinkedHashMap<>(); private Builder() {} @@ -150,7 +153,6 @@ public Builder from(MapExample other) { return this; } - @JsonSetter(value = "items", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder items(@Nonnull Map items) { checkNotBuilt(); this.items = new LinkedHashMap<>(Preconditions.checkNotNull(items, "items cannot be null")); @@ -169,7 +171,6 @@ public Builder items(String key, String value) { return this; } - @JsonSetter(value = "optionalItems", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) public Builder optionalItems(@Nonnull Map> optionalItems) { checkNotBuilt(); this.optionalItems = @@ -189,7 +190,6 @@ public Builder optionalItems(String key, Optional value) { return this; } - @JsonSetter(value = "aliasOptionalItems", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) public Builder aliasOptionalItems(@Nonnull Map aliasOptionalItems) { checkNotBuilt(); this.aliasOptionalItems = new LinkedHashMap<>( diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java index d4b7cc81a..3cb47a64c 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java @@ -6,14 +6,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -24,6 +33,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = SingleUnion.Deserializer.class) public final class SingleUnion { private final Base value; @@ -154,19 +164,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes(@JsonSubTypes.Type(FooWrapper.class)) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("foo") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class FooWrapper implements Base { private final String value; @@ -270,4 +273,126 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public SingleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + SingleUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SingleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private SingleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SingleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SingleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(SingleUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private SingleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "foo" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new SingleUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static SingleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SingleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new SingleUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java index 56af03ca9..2cf0aadfb 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java @@ -6,14 +6,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,6 +34,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = Union.Deserializer.class) public final class Union { private final Base value; @@ -231,23 +241,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(FooWrapper.class), - @JsonSubTypes.Type(BarWrapper.class), - @JsonSubTypes.Type(BazWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("foo") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class FooWrapper implements Base { private final String value; @@ -293,6 +292,7 @@ public String toString() { } @JsonTypeName("bar") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BarWrapper implements Base { private final int value; @@ -339,6 +339,7 @@ public String toString() { } @JsonTypeName("baz") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BazWrapper implements Base { private final long value; @@ -443,4 +444,127 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = + new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public Union deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch(Union.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch(Union.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private Union deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + Union.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + Union.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(Union.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private Union deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "foo" -> 0; + case "bar" -> 1; + case "baz" -> 2; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new Union((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static Union deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + Union.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Union(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java index b0d2f2300..ef70e4e86 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java @@ -6,17 +6,25 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.Unsafe; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; @@ -35,6 +43,7 @@ /** A type which can either be a StringExample, a set of strings, or an integer. */ @Unsafe @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = UnionTypeExample.Deserializer.class) public final class UnionTypeExample { private final Base value; @@ -695,39 +704,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(StringExampleWrapper.class), - @JsonSubTypes.Type(ThisFieldIsAnIntegerWrapper.class), - @JsonSubTypes.Type(AlsoAnIntegerWrapper.class), - @JsonSubTypes.Type(IfWrapper.class), - @JsonSubTypes.Type(NewWrapper.class), - @JsonSubTypes.Type(InterfaceWrapper.class), - @JsonSubTypes.Type(CompletedWrapper.class), - @JsonSubTypes.Type(Unknown_Wrapper.class), - @JsonSubTypes.Type(OptionalWrapper.class), - @JsonSubTypes.Type(ListWrapper.class), - @JsonSubTypes.Type(SetWrapper.class), - @JsonSubTypes.Type(MapWrapper.class), - @JsonSubTypes.Type(OptionalAliasWrapper.class), - @JsonSubTypes.Type(ListAliasWrapper.class), - @JsonSubTypes.Type(SetAliasWrapper.class), - @JsonSubTypes.Type(MapAliasWrapper.class), - @JsonSubTypes.Type(BooleanFieldWrapper.class), - @JsonSubTypes.Type(SafeIntWrapper.class), - @JsonSubTypes.Type(UnsafeDoubleWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("stringExample") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class StringExampleWrapper implements Base { private final StringExample value; @@ -773,6 +755,7 @@ public String toString() { } @JsonTypeName("thisFieldIsAnInteger") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ThisFieldIsAnIntegerWrapper implements Base { private final int value; @@ -819,6 +802,7 @@ public String toString() { } @JsonTypeName("alsoAnInteger") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class AlsoAnIntegerWrapper implements Base { private final int value; @@ -864,6 +848,7 @@ public String toString() { } @JsonTypeName("if") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class IfWrapper implements Base { private final int value; @@ -909,6 +894,7 @@ public String toString() { } @JsonTypeName("new") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class NewWrapper implements Base { private final int value; @@ -954,6 +940,7 @@ public String toString() { } @JsonTypeName("interface") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class InterfaceWrapper implements Base { private final int value; @@ -999,6 +986,7 @@ public String toString() { } @JsonTypeName("completed") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class CompletedWrapper implements Base { private final int value; @@ -1044,6 +1032,7 @@ public String toString() { } @JsonTypeName("unknown") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class Unknown_Wrapper implements Base { private final int value; @@ -1089,6 +1078,7 @@ public String toString() { } @JsonTypeName("optional") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class OptionalWrapper implements Base { private final Optional value; @@ -1135,6 +1125,7 @@ public String toString() { } @JsonTypeName("list") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ListWrapper implements Base { private final List value; @@ -1180,6 +1171,7 @@ public String toString() { } @JsonTypeName("set") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SetWrapper implements Base { private final Set value; @@ -1227,6 +1219,7 @@ public String toString() { } @JsonTypeName("map") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class MapWrapper implements Base { private final Map value; @@ -1272,6 +1265,7 @@ public String toString() { } @JsonTypeName("optionalAlias") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class OptionalAliasWrapper implements Base { private final OptionalAlias value; @@ -1318,6 +1312,7 @@ public String toString() { } @JsonTypeName("listAlias") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ListAliasWrapper implements Base { private final ListAlias value; @@ -1363,6 +1358,7 @@ public String toString() { } @JsonTypeName("setAlias") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SetAliasWrapper implements Base { private final SetAlias value; @@ -1408,6 +1404,7 @@ public String toString() { } @JsonTypeName("mapAlias") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class MapAliasWrapper implements Base { private final MapAliasExample value; @@ -1454,6 +1451,7 @@ public String toString() { } @JsonTypeName("booleanField") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BooleanFieldWrapper implements Base { private final boolean value; @@ -1499,6 +1497,7 @@ public String toString() { } @JsonTypeName("safeInt") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SafeIntWrapper implements Base { private final int value; @@ -1544,6 +1543,7 @@ public String toString() { } @JsonTypeName("unsafeDouble") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class UnsafeDoubleWrapper implements Base { private final double value; @@ -1647,4 +1647,165 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] { + StringExampleWrapper.class, + ThisFieldIsAnIntegerWrapper.class, + AlsoAnIntegerWrapper.class, + IfWrapper.class, + NewWrapper.class, + InterfaceWrapper.class, + CompletedWrapper.class, + Unknown_Wrapper.class, + OptionalWrapper.class, + ListWrapper.class, + SetWrapper.class, + MapWrapper.class, + OptionalAliasWrapper.class, + ListAliasWrapper.class, + SetAliasWrapper.class, + MapAliasWrapper.class, + BooleanFieldWrapper.class, + SafeIntWrapper.class, + UnsafeDoubleWrapper.class + }; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public UnionTypeExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + UnionTypeExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionTypeExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private UnionTypeExample deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionTypeExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(UnionTypeExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "stringExample" -> 0; + case "thisFieldIsAnInteger" -> 1; + case "alsoAnInteger" -> 2; + case "if" -> 3; + case "new" -> 4; + case "interface" -> 5; + case "completed" -> 6; + case "unknown" -> 7; + case "optional" -> 8; + case "list" -> 9; + case "set" -> 10; + case "map" -> 11; + case "optionalAlias" -> 12; + case "listAlias" -> 13; + case "setAlias" -> 14; + case "mapAlias" -> 15; + case "booleanField" -> 16; + case "safeInt" -> 17; + case "unsafeDouble" -> 18; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new UnionTypeExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static UnionTypeExample deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + } + return new UnionTypeExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java index 1f7d8cee7..e7a0e5f3b 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java @@ -6,14 +6,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -24,6 +33,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = UnionWithUnknownString.Deserializer.class) public final class UnionWithUnknownString { private final Base value; @@ -155,19 +165,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes(@JsonSubTypes.Type(Unknown_Wrapper.class)) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("unknown") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class Unknown_Wrapper implements Base { private final String value; @@ -271,4 +274,129 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {Unknown_Wrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public UnionWithUnknownString deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + UnionWithUnknownString.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private UnionWithUnknownString deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionWithUnknownString.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(UnionWithUnknownString.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private UnionWithUnknownString deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "unknown" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new UnionWithUnknownString((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static UnionWithUnknownString deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionWithUnknownString.class, "Expected the end of a JSON object while deserializing a union"); + } + return new UnionWithUnknownString(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/bothstrictstagedbuilder/com/palantir/product/StrictFourFields.java b/conjure-java-core/src/integrationInput/java/bothstrictstagedbuilder/com/palantir/product/StrictFourFields.java index d11fe9abe..6cd13a3f3 100644 --- a/conjure-java-core/src/integrationInput/java/bothstrictstagedbuilder/com/palantir/product/StrictFourFields.java +++ b/conjure-java-core/src/integrationInput/java/bothstrictstagedbuilder/com/palantir/product/StrictFourFields.java @@ -206,6 +206,7 @@ static final class DefaultBuilder implements Builder { private Optional optionalItem = Optional.empty(); + @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) private Map mappedRids = new LinkedHashMap<>(); private DefaultBuilder() {} @@ -252,7 +253,6 @@ public Builder optionalItem(@Nonnull String optionalItem) { } @Override - @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) public Builder mappedRids(@Nonnull Map mappedRids) { checkNotBuilt(); this.mappedRids = new LinkedHashMap<>(Preconditions.checkNotNull(mappedRids, "mappedRids cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/bothstrictstagedbuilder/com/palantir/product/StrictMultipleDeprecatedAndUnsafeFields.java b/conjure-java-core/src/integrationInput/java/bothstrictstagedbuilder/com/palantir/product/StrictMultipleDeprecatedAndUnsafeFields.java index 1fc34eb77..8318bf766 100644 --- a/conjure-java-core/src/integrationInput/java/bothstrictstagedbuilder/com/palantir/product/StrictMultipleDeprecatedAndUnsafeFields.java +++ b/conjure-java-core/src/integrationInput/java/bothstrictstagedbuilder/com/palantir/product/StrictMultipleDeprecatedAndUnsafeFields.java @@ -287,6 +287,7 @@ static final class DefaultBuilder implements Builder { private Optional<@Unsafe String> optionalItem = Optional.empty(); + @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) private Map mappedRids = new LinkedHashMap<>(); private StrictFourFields strictFourFieldsObject; @@ -365,7 +366,6 @@ public Builder optionalItem(@Nonnull @Unsafe String optionalItem) { } @Override - @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) public Builder mappedRids(@Nonnull Map mappedRids) { checkNotBuilt(); this.mappedRids = new LinkedHashMap<>(Preconditions.checkNotNull(mappedRids, "mappedRids cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java index aa5c37790..ee5d4bbe2 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java @@ -6,16 +6,25 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -27,6 +36,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = ExampleDefensiveCollectionListsUnion.Deserializer.class) public final class ExampleDefensiveCollectionListsUnion { private final Base value; @@ -214,23 +224,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(ListWrapper.class), - @JsonSubTypes.Type(PrimitiveListWrapper.class), - @JsonSubTypes.Type(ListOptionalWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("list") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ListWrapper implements Base { private final List value; @@ -276,6 +275,7 @@ public String toString() { } @JsonTypeName("primitiveList") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class PrimitiveListWrapper implements Base { private final List value; @@ -322,6 +322,7 @@ public String toString() { } @JsonTypeName("listOptional") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ListOptionalWrapper implements Base { private final List> value; @@ -426,4 +427,136 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer + implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = + new Class[] {ListWrapper.class, PrimitiveListWrapper.class, ListOptionalWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public ExampleDefensiveCollectionListsUnion deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private ExampleDefensiveCollectionListsUnion deserializeBuffered( + JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, + "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private ExampleDefensiveCollectionListsUnion deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "list" -> 0; + case "primitiveList" -> 1; + case "listOptional" -> 2; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new ExampleDefensiveCollectionListsUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static ExampleDefensiveCollectionListsUnion deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + return new ExampleDefensiveCollectionListsUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java index f13c83cfd..c0d6c8848 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java @@ -6,15 +6,24 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -26,6 +35,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = ExampleDefensiveCollectionMapsUnion.Deserializer.class) public final class ExampleDefensiveCollectionMapsUnion { private final Base value; @@ -184,19 +194,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({@JsonSubTypes.Type(MapWrapper.class), @JsonSubTypes.Type(MapOptionalWrapper.class)}) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("map") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class MapWrapper implements Base { private final Map value; @@ -244,6 +247,7 @@ public String toString() { } @JsonTypeName("mapOptional") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class MapOptionalWrapper implements Base { private final Map> value; @@ -349,4 +353,134 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer + implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {MapWrapper.class, MapOptionalWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public ExampleDefensiveCollectionMapsUnion deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private ExampleDefensiveCollectionMapsUnion deserializeBuffered( + JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, + "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private ExampleDefensiveCollectionMapsUnion deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "map" -> 0; + case "mapOptional" -> 1; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new ExampleDefensiveCollectionMapsUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static ExampleDefensiveCollectionMapsUnion deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + return new ExampleDefensiveCollectionMapsUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java index 46558cc99..ebf32d516 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java @@ -6,17 +6,25 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; @@ -28,6 +36,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = ExampleDefensiveCollectionSetsUnion.Deserializer.class) public final class ExampleDefensiveCollectionSetsUnion { private final Base value; @@ -153,19 +162,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes(@JsonSubTypes.Type(SetWrapper.class)) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("set") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SetWrapper implements Base { private final Set value; @@ -271,4 +273,133 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer + implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {SetWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public ExampleDefensiveCollectionSetsUnion deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private ExampleDefensiveCollectionSetsUnion deserializeBuffered( + JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, + "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private ExampleDefensiveCollectionSetsUnion deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "set" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new ExampleDefensiveCollectionSetsUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static ExampleDefensiveCollectionSetsUnion deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + return new ExampleDefensiveCollectionSetsUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java index 59d2800bd..1d0c6475b 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java @@ -6,16 +6,25 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -27,6 +36,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = ExampleDefensiveCollectionListsUnion.Deserializer.class) public final class ExampleDefensiveCollectionListsUnion { private final Base value; @@ -214,23 +224,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(ListWrapper.class), - @JsonSubTypes.Type(PrimitiveListWrapper.class), - @JsonSubTypes.Type(ListOptionalWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("list") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ListWrapper implements Base { private final List value; @@ -276,6 +275,7 @@ public String toString() { } @JsonTypeName("primitiveList") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class PrimitiveListWrapper implements Base { private final List value; @@ -322,6 +322,7 @@ public String toString() { } @JsonTypeName("listOptional") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ListOptionalWrapper implements Base { private final List> value; @@ -426,4 +427,136 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer + implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = + new Class[] {ListWrapper.class, PrimitiveListWrapper.class, ListOptionalWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public ExampleDefensiveCollectionListsUnion deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private ExampleDefensiveCollectionListsUnion deserializeBuffered( + JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, + "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private ExampleDefensiveCollectionListsUnion deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "list" -> 0; + case "primitiveList" -> 1; + case "listOptional" -> 2; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new ExampleDefensiveCollectionListsUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static ExampleDefensiveCollectionListsUnion deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionListsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + return new ExampleDefensiveCollectionListsUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java index 89b8b126b..be925f0ee 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java @@ -6,15 +6,24 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -26,6 +35,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = ExampleDefensiveCollectionMapsUnion.Deserializer.class) public final class ExampleDefensiveCollectionMapsUnion { private final Base value; @@ -184,19 +194,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({@JsonSubTypes.Type(MapWrapper.class), @JsonSubTypes.Type(MapOptionalWrapper.class)}) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("map") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class MapWrapper implements Base { private final Map value; @@ -242,6 +245,7 @@ public String toString() { } @JsonTypeName("mapOptional") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class MapOptionalWrapper implements Base { private final Map> value; @@ -347,4 +351,134 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer + implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {MapWrapper.class, MapOptionalWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public ExampleDefensiveCollectionMapsUnion deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private ExampleDefensiveCollectionMapsUnion deserializeBuffered( + JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, + "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private ExampleDefensiveCollectionMapsUnion deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "map" -> 0; + case "mapOptional" -> 1; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new ExampleDefensiveCollectionMapsUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static ExampleDefensiveCollectionMapsUnion deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionMapsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + return new ExampleDefensiveCollectionMapsUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java index 1ca7a45d5..e5473d0a9 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java @@ -6,17 +6,25 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; @@ -28,6 +36,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = ExampleDefensiveCollectionSetsUnion.Deserializer.class) public final class ExampleDefensiveCollectionSetsUnion { private final Base value; @@ -153,19 +162,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes(@JsonSubTypes.Type(SetWrapper.class)) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("set") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SetWrapper implements Base { private final Set value; @@ -271,4 +273,133 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer + implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {SetWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public ExampleDefensiveCollectionSetsUnion deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private ExampleDefensiveCollectionSetsUnion deserializeBuffered( + JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, + "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private ExampleDefensiveCollectionSetsUnion deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "set" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new ExampleDefensiveCollectionSetsUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static ExampleDefensiveCollectionSetsUnion deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExampleDefensiveCollectionSetsUnion.class, + "Expected the end of a JSON object while deserializing a union"); + } + return new ExampleDefensiveCollectionSetsUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/AnyExample.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/AnyExample.java index 02449329c..1804f54d2 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/AnyExample.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/AnyExample.java @@ -111,6 +111,7 @@ public static final class Builder { private Object anyValue; + @JsonSetter(value = "anyMap", nulls = Nulls.SKIP) private Map anyMap = new LinkedHashMap<>(); private Builder() {} @@ -129,7 +130,6 @@ public Builder anyValue(@Nonnull Object anyValue) { return this; } - @JsonSetter(value = "anyMap", nulls = Nulls.SKIP) public Builder anyMap(@Nonnull Map anyMap) { checkNotBuilt(); this.anyMap = new LinkedHashMap<>(Preconditions.checkNotNull(anyMap, "anyMap cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/CollectionExample.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/CollectionExample.java index 120decf47..fc338a4eb 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/CollectionExample.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/CollectionExample.java @@ -132,6 +132,7 @@ public static final class Builder { private Set stringSet = ConjureCollections.newSet(); + @JsonSetter(value = "stringMap", nulls = Nulls.SKIP) private Map stringMap = new LinkedHashMap<>(); private Builder() {} @@ -186,7 +187,6 @@ public Builder stringSet(String stringSet) { return this; } - @JsonSetter(value = "stringMap", nulls = Nulls.SKIP) public Builder stringMap(@Nonnull Map stringMap) { checkNotBuilt(); this.stringMap = new LinkedHashMap<>(Preconditions.checkNotNull(stringMap, "stringMap cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/ComplexExample.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/ComplexExample.java index 590b5797b..a6d63bfea 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/ComplexExample.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/ComplexExample.java @@ -140,6 +140,7 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; + @JsonSetter(value = "metadata", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) private Map>> metadata = new LinkedHashMap<>(); private EnumExample status; @@ -159,7 +160,6 @@ public Builder from(ComplexExample other) { return this; } - @JsonSetter(value = "metadata", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) public Builder metadata(@Nonnull Map>> metadata) { checkNotBuilt(); this.metadata = new LinkedHashMap<>(Preconditions.checkNotNull(metadata, "metadata cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java index f5316d77b..6480036b0 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java @@ -5,12 +5,25 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -20,13 +33,8 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = EmptyUnion.Unknown.class) -@JsonSubTypes({}) +@JsonDeserialize(using = EmptyUnion.Deserializer.class) +@JsonSerialize(using = EmptyUnion.Serializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class EmptyUnion permits EmptyUnion.Unknown { public static EmptyUnion unknown(@Safe String type, Object value) { @@ -38,6 +46,8 @@ public static EmptyUnion unknown(@Safe String type, Object value) { public abstract T accept(Visitor visitor); + @JsonDeserialize + @JsonSerialize public static final class Unknown extends EmptyUnion { private final String type; @@ -98,6 +108,134 @@ public String toString() { } } + static final class Serializer extends JsonSerializer { + @Override + public void serialize(EmptyUnion value, JsonGenerator generator, SerializerProvider serializers) + throws IOException { + serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); + } + } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + EmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return (EmptyUnion) deserializer.deserialize(parser, context); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Unknown(type, values); + } + } + public interface Visitor { T visitUnknown(@Safe String unknownType); diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/NestedCollectionExample.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/NestedCollectionExample.java index ca396af10..249fa54ec 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/NestedCollectionExample.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/NestedCollectionExample.java @@ -138,8 +138,10 @@ public static final class Builder { private List> nestedList = ConjureCollections.newList(); + @JsonSetter(value = "nestedMap", nulls = Nulls.SKIP) private Map> nestedMap = new LinkedHashMap<>(); + @JsonSetter(value = "mixedCollection", nulls = Nulls.SKIP) private Map> mixedCollection = new LinkedHashMap<>(); private Builder() {} @@ -173,7 +175,6 @@ public Builder nestedList(List nestedList) { return this; } - @JsonSetter(value = "nestedMap", nulls = Nulls.SKIP) public Builder nestedMap(@Nonnull Map> nestedMap) { checkNotBuilt(); this.nestedMap = new LinkedHashMap<>(Preconditions.checkNotNull(nestedMap, "nestedMap cannot be null")); @@ -192,7 +193,6 @@ public Builder nestedMap(String key, Map value) { return this; } - @JsonSetter(value = "mixedCollection", nulls = Nulls.SKIP) public Builder mixedCollection(@Nonnull Map> mixedCollection) { checkNotBuilt(); this.mixedCollection = diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java index 757a4fc4d..72905bc1d 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java @@ -6,14 +6,27 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -24,17 +37,8 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = SimpleUnion.Unknown.class) -@JsonSubTypes({ - @JsonSubTypes.Type(value = SimpleUnion.Foo.class, name = "foo"), - @JsonSubTypes.Type(value = SimpleUnion.Bar.class, name = "bar"), - @JsonSubTypes.Type(value = SimpleUnion.Baz.class, name = "baz") -}) +@JsonDeserialize(using = SimpleUnion.Deserializer.class) +@JsonSerialize(using = SimpleUnion.Serializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class SimpleUnion permits SimpleUnion.Foo, SimpleUnion.Bar, SimpleUnion.Baz, SimpleUnion.Unknown { @@ -80,6 +84,9 @@ public Known throwOnUnknown() { public sealed interface Known permits Foo, Bar, Baz {} @JsonTypeName("foo") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Foo extends SimpleUnion implements Known { private final String value; @@ -125,6 +132,9 @@ public String toString() { } @JsonTypeName("bar") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Bar extends SimpleUnion implements Known { private final int value; @@ -170,6 +180,9 @@ public String toString() { } @JsonTypeName("baz") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Baz extends SimpleUnion implements Known { private final SafeLong value; @@ -214,6 +227,8 @@ public String toString() { } } + @JsonDeserialize + @JsonSerialize public static final class Unknown extends SimpleUnion { private final String type; @@ -274,6 +289,138 @@ public String toString() { } } + static final class Serializer extends JsonSerializer { + @Override + public void serialize(SimpleUnion value, JsonGenerator generator, SerializerProvider serializers) + throws IOException { + serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); + } + } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {Foo.class, Bar.class, Baz.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "foo" -> 0; + case "bar" -> 1; + case "baz" -> 2; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return (SimpleUnion) deserializer.deserialize(parser, context); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Unknown(type, values); + } + } + public interface Visitor { T visitFoo(String value); diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java index 6a277abc2..b78cdfb19 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java @@ -6,14 +6,27 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -26,19 +39,8 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnionExample.Unknown.class) -@JsonSubTypes({ - @JsonSubTypes.Type(value = UnionExample.StringVariant.class, name = "stringVariant"), - @JsonSubTypes.Type(value = UnionExample.IntVariant.class, name = "intVariant"), - @JsonSubTypes.Type(value = UnionExample.ObjectVariant.class, name = "objectVariant"), - @JsonSubTypes.Type(value = UnionExample.CollectionVariant.class, name = "collectionVariant"), - @JsonSubTypes.Type(value = UnionExample.OptionalVariant.class, name = "optionalVariant") -}) +@JsonDeserialize(using = UnionExample.Deserializer.class) +@JsonSerialize(using = UnionExample.Serializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class UnionExample permits UnionExample.StringVariant, @@ -104,6 +106,9 @@ public sealed interface Known permits StringVariant, IntVariant, ObjectVariant, CollectionVariant, OptionalVariant {} @JsonTypeName("stringVariant") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class StringVariant extends UnionExample implements Known { private final String value; @@ -149,6 +154,9 @@ public String toString() { } @JsonTypeName("intVariant") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class IntVariant extends UnionExample implements Known { private final int value; @@ -194,6 +202,9 @@ public String toString() { } @JsonTypeName("objectVariant") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class ObjectVariant extends UnionExample implements Known { private final ObjectReference value; @@ -239,6 +250,9 @@ public String toString() { } @JsonTypeName("collectionVariant") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class CollectionVariant extends UnionExample implements Known { private final List value; @@ -285,6 +299,9 @@ public String toString() { } @JsonTypeName("optionalVariant") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class OptionalVariant extends UnionExample implements Known { private final Optional value; @@ -330,6 +347,8 @@ public String toString() { } } + @JsonDeserialize + @JsonSerialize public static final class Unknown extends UnionExample { private final String type; @@ -390,6 +409,142 @@ public String toString() { } } + static final class Serializer extends JsonSerializer { + @Override + public void serialize(UnionExample value, JsonGenerator generator, SerializerProvider serializers) + throws IOException { + serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); + } + } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] { + StringVariant.class, IntVariant.class, ObjectVariant.class, CollectionVariant.class, OptionalVariant.class + }; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public UnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + UnionExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private UnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(UnionExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "stringVariant" -> 0; + case "intVariant" -> 1; + case "objectVariant" -> 2; + case "collectionVariant" -> 3; + case "optionalVariant" -> 4; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return (UnionExample) deserializer.deserialize(parser, context); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Unknown(type, values); + } + } + public interface Visitor { T visitStringVariant(String value); diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/AnyExample.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/AnyExample.java index 6aef01025..ed6c5dd13 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/AnyExample.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/AnyExample.java @@ -111,6 +111,7 @@ public static final class Builder { private Object anyValue; + @JsonSetter(value = "anyMap", nulls = Nulls.SKIP) private Map anyMap = new LinkedHashMap<>(); private Builder() {} @@ -129,7 +130,6 @@ public Builder anyValue(@Nonnull Object anyValue) { return this; } - @JsonSetter(value = "anyMap", nulls = Nulls.SKIP) public Builder anyMap(@Nonnull Map anyMap) { checkNotBuilt(); this.anyMap = new LinkedHashMap<>(Preconditions.checkNotNull(anyMap, "anyMap cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/CollectionExample.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/CollectionExample.java index 03ff4708e..dc14b9ca8 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/CollectionExample.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/CollectionExample.java @@ -132,6 +132,7 @@ public static final class Builder { private Set stringSet = ConjureCollections.newSet(); + @JsonSetter(value = "stringMap", nulls = Nulls.SKIP) private Map stringMap = new LinkedHashMap<>(); private Builder() {} @@ -186,7 +187,6 @@ public Builder stringSet(String stringSet) { return this; } - @JsonSetter(value = "stringMap", nulls = Nulls.SKIP) public Builder stringMap(@Nonnull Map stringMap) { checkNotBuilt(); this.stringMap = new LinkedHashMap<>(Preconditions.checkNotNull(stringMap, "stringMap cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/ComplexExample.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/ComplexExample.java index b80256a27..ed83f0bcc 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/ComplexExample.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/ComplexExample.java @@ -142,6 +142,7 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; + @JsonSetter(value = "metadata", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) private Map>> metadata = new LinkedHashMap<>(); private EnumExample status; @@ -161,7 +162,6 @@ public Builder from(ComplexExample other) { return this; } - @JsonSetter(value = "metadata", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) public Builder metadata(@Nonnull Map>> metadata) { checkNotBuilt(); this.metadata = new LinkedHashMap<>(Preconditions.checkNotNull(metadata, "metadata cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java index 1e8079a97..18ca505dc 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java @@ -3,14 +3,23 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -20,6 +29,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = EmptyUnion.Deserializer.class) public final class EmptyUnion { private final Base value; @@ -113,13 +123,6 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @@ -183,4 +186,124 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + EmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new EmptyUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new EmptyUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/NestedCollectionExample.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/NestedCollectionExample.java index 762c83bf4..bc6954cbe 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/NestedCollectionExample.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/NestedCollectionExample.java @@ -138,8 +138,10 @@ public static final class Builder { private List> nestedList = ConjureCollections.newList(); + @JsonSetter(value = "nestedMap", nulls = Nulls.SKIP) private Map> nestedMap = new LinkedHashMap<>(); + @JsonSetter(value = "mixedCollection", nulls = Nulls.SKIP) private Map> mixedCollection = new LinkedHashMap<>(); private Builder() {} @@ -173,7 +175,6 @@ public Builder nestedList(List nestedList) { return this; } - @JsonSetter(value = "nestedMap", nulls = Nulls.SKIP) public Builder nestedMap(@Nonnull Map> nestedMap) { checkNotBuilt(); this.nestedMap = new LinkedHashMap<>(Preconditions.checkNotNull(nestedMap, "nestedMap cannot be null")); @@ -192,7 +193,6 @@ public Builder nestedMap(String key, Map value) { return this; } - @JsonSetter(value = "mixedCollection", nulls = Nulls.SKIP) public Builder mixedCollection(@Nonnull Map> mixedCollection) { checkNotBuilt(); this.mixedCollection = diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java index 41bb4e582..d8844c6f1 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java @@ -6,15 +6,24 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -27,6 +36,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = UnionExample.Deserializer.class) public final class UnionExample { private final Base value; @@ -270,25 +280,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(StringVariantWrapper.class), - @JsonSubTypes.Type(IntVariantWrapper.class), - @JsonSubTypes.Type(ObjectVariantWrapper.class), - @JsonSubTypes.Type(CollectionVariantWrapper.class), - @JsonSubTypes.Type(OptionalVariantWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("stringVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class StringVariantWrapper implements Base { private final String value; @@ -334,6 +331,7 @@ public String toString() { } @JsonTypeName("intVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class IntVariantWrapper implements Base { private final int value; @@ -379,6 +377,7 @@ public String toString() { } @JsonTypeName("objectVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ObjectVariantWrapper implements Base { private final ObjectReference value; @@ -424,6 +423,7 @@ public String toString() { } @JsonTypeName("collectionVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class CollectionVariantWrapper implements Base { private final List value; @@ -471,6 +471,7 @@ public String toString() { } @JsonTypeName("optionalVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class OptionalVariantWrapper implements Base { private final Optional value; @@ -576,4 +577,136 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] { + StringVariantWrapper.class, + IntVariantWrapper.class, + ObjectVariantWrapper.class, + CollectionVariantWrapper.class, + OptionalVariantWrapper.class + }; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public UnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + UnionExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private UnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(UnionExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "stringVariant" -> 0; + case "intVariant" -> 1; + case "objectVariant" -> 2; + case "collectionVariant" -> 3; + case "optionalVariant" -> 4; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new UnionExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + return new UnionExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/AnyExample.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/AnyExample.java index 0bd9fe5e0..267b3b889 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/AnyExample.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/AnyExample.java @@ -111,6 +111,7 @@ public static final class Builder { private Object anyValue; + @JsonSetter(value = "anyMap", nulls = Nulls.SKIP) private Map anyMap = new LinkedHashMap<>(); private Builder() {} @@ -129,7 +130,6 @@ public Builder anyValue(@Nonnull Object anyValue) { return this; } - @JsonSetter(value = "anyMap", nulls = Nulls.SKIP) public Builder anyMap(@Nonnull Map anyMap) { checkNotBuilt(); this.anyMap = new LinkedHashMap<>(Preconditions.checkNotNull(anyMap, "anyMap cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/CollectionExample.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/CollectionExample.java index 7848db1f8..ad84739aa 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/CollectionExample.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/CollectionExample.java @@ -132,6 +132,7 @@ public static final class Builder { private Set stringSet = ConjureCollections.newSet(); + @JsonSetter(value = "stringMap", nulls = Nulls.SKIP) private Map stringMap = new LinkedHashMap<>(); private Builder() {} @@ -186,7 +187,6 @@ public Builder stringSet(String stringSet) { return this; } - @JsonSetter(value = "stringMap", nulls = Nulls.SKIP) public Builder stringMap(@Nonnull Map stringMap) { checkNotBuilt(); this.stringMap = new LinkedHashMap<>(Preconditions.checkNotNull(stringMap, "stringMap cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/ComplexExample.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/ComplexExample.java index 54f883c49..3a8966203 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/ComplexExample.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/ComplexExample.java @@ -140,6 +140,7 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; + @JsonSetter(value = "metadata", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) private Map>> metadata = new LinkedHashMap<>(); private EnumExample status; @@ -159,7 +160,6 @@ public Builder from(ComplexExample other) { return this; } - @JsonSetter(value = "metadata", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) public Builder metadata(@Nonnull Map>> metadata) { checkNotBuilt(); this.metadata = new LinkedHashMap<>(Preconditions.checkNotNull(metadata, "metadata cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java index 0da7d0aa5..5c860aa83 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java @@ -3,14 +3,23 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -20,6 +29,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = EmptyUnion.Deserializer.class) public final class EmptyUnion { private final Base value; @@ -113,13 +123,6 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @@ -183,4 +186,124 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + EmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new EmptyUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new EmptyUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/NestedCollectionExample.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/NestedCollectionExample.java index 242b1df8e..688806d42 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/NestedCollectionExample.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/NestedCollectionExample.java @@ -138,8 +138,10 @@ public static final class Builder { private List> nestedList = ConjureCollections.newList(); + @JsonSetter(value = "nestedMap", nulls = Nulls.SKIP) private Map> nestedMap = new LinkedHashMap<>(); + @JsonSetter(value = "mixedCollection", nulls = Nulls.SKIP) private Map> mixedCollection = new LinkedHashMap<>(); private Builder() {} @@ -173,7 +175,6 @@ public Builder nestedList(List nestedList) { return this; } - @JsonSetter(value = "nestedMap", nulls = Nulls.SKIP) public Builder nestedMap(@Nonnull Map> nestedMap) { checkNotBuilt(); this.nestedMap = new LinkedHashMap<>(Preconditions.checkNotNull(nestedMap, "nestedMap cannot be null")); @@ -192,7 +193,6 @@ public Builder nestedMap(String key, Map value) { return this; } - @JsonSetter(value = "mixedCollection", nulls = Nulls.SKIP) public Builder mixedCollection(@Nonnull Map> mixedCollection) { checkNotBuilt(); this.mixedCollection = diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java index 7adec5ce9..25741e338 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java @@ -6,15 +6,24 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,6 +34,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = SimpleUnion.Deserializer.class) public final class SimpleUnion { private final Base value; @@ -206,23 +216,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(FooWrapper.class), - @JsonSubTypes.Type(BarWrapper.class), - @JsonSubTypes.Type(BazWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("foo") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class FooWrapper implements Base { private final String value; @@ -268,6 +267,7 @@ public String toString() { } @JsonTypeName("bar") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BarWrapper implements Base { private final int value; @@ -313,6 +313,7 @@ public String toString() { } @JsonTypeName("baz") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BazWrapper implements Base { private final SafeLong value; @@ -416,4 +417,129 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = + new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "foo" -> 0; + case "bar" -> 1; + case "baz" -> 2; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new SimpleUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new SimpleUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java index 8575892d4..b508d12f2 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java @@ -6,15 +6,24 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -27,6 +36,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = UnionExample.Deserializer.class) public final class UnionExample { private final Base value; @@ -270,25 +280,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(StringVariantWrapper.class), - @JsonSubTypes.Type(IntVariantWrapper.class), - @JsonSubTypes.Type(ObjectVariantWrapper.class), - @JsonSubTypes.Type(CollectionVariantWrapper.class), - @JsonSubTypes.Type(OptionalVariantWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("stringVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class StringVariantWrapper implements Base { private final String value; @@ -334,6 +331,7 @@ public String toString() { } @JsonTypeName("intVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class IntVariantWrapper implements Base { private final int value; @@ -379,6 +377,7 @@ public String toString() { } @JsonTypeName("objectVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ObjectVariantWrapper implements Base { private final ObjectReference value; @@ -424,6 +423,7 @@ public String toString() { } @JsonTypeName("collectionVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class CollectionVariantWrapper implements Base { private final List value; @@ -471,6 +471,7 @@ public String toString() { } @JsonTypeName("optionalVariant") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class OptionalVariantWrapper implements Base { private final Optional value; @@ -576,4 +577,136 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] { + StringVariantWrapper.class, + IntVariantWrapper.class, + ObjectVariantWrapper.class, + CollectionVariantWrapper.class, + OptionalVariantWrapper.class + }; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public UnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + UnionExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private UnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(UnionExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "stringVariant" -> 0; + case "intVariant" -> 1; + case "objectVariant" -> 2; + case "collectionVariant" -> 3; + case "optionalVariant" -> 4; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new UnionExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + return new UnionExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/excludeasyncinterfaces/com/palantir/product/datasets/BackingFileSystem.java b/conjure-java-core/src/integrationInput/java/excludeasyncinterfaces/com/palantir/product/datasets/BackingFileSystem.java index cf128f388..4fa981a53 100644 --- a/conjure-java-core/src/integrationInput/java/excludeasyncinterfaces/com/palantir/product/datasets/BackingFileSystem.java +++ b/conjure-java-core/src/integrationInput/java/excludeasyncinterfaces/com/palantir/product/datasets/BackingFileSystem.java @@ -131,6 +131,7 @@ public static final class Builder { private String baseUri; + @JsonSetter(value = "configuration", nulls = Nulls.SKIP) private Map configuration = new LinkedHashMap<>(); private Builder() {} @@ -158,7 +159,6 @@ public Builder baseUri(@Nonnull String baseUri) { return this; } - @JsonSetter(value = "configuration", nulls = Nulls.SKIP) public Builder configuration(@Nonnull Map configuration) { checkNotBuilt(); this.configuration = diff --git a/conjure-java-core/src/integrationInput/java/excludeemptycollections/com/palantir/product/CollectionsTestObject.java b/conjure-java-core/src/integrationInput/java/excludeemptycollections/com/palantir/product/CollectionsTestObject.java index 0c2ae2774..f03c2c7fb 100644 --- a/conjure-java-core/src/integrationInput/java/excludeemptycollections/com/palantir/product/CollectionsTestObject.java +++ b/conjure-java-core/src/integrationInput/java/excludeemptycollections/com/palantir/product/CollectionsTestObject.java @@ -188,6 +188,7 @@ public static final class Builder { private List items = ConjureCollections.newList(); + @JsonSetter(value = "itemsMap", nulls = Nulls.SKIP) private Map itemsMap = new LinkedHashMap<>(); private Optional optionalItem = Optional.empty(); @@ -233,7 +234,6 @@ public Builder items(String items) { return this; } - @JsonSetter(value = "itemsMap", nulls = Nulls.SKIP) public Builder itemsMap(@Nonnull Map itemsMap) { checkNotBuilt(); this.itemsMap = new LinkedHashMap<>(Preconditions.checkNotNull(itemsMap, "itemsMap cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java b/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java index 0028521e1..67807dd9e 100644 --- a/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java +++ b/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java @@ -6,14 +6,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,6 +34,7 @@ /** A type which can either be a StringExample, a set of strings, or an integer. */ @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = UnionWithExternal.Deserializer.class) public final class UnionWithExternal { private final Base value; @@ -155,19 +165,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes(@JsonSubTypes.Type(FieldWrapper.class)) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("field") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class FieldWrapper implements Base { private final String value; @@ -271,4 +274,127 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {FieldWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public UnionWithExternal deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + UnionWithExternal.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionWithExternal.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private UnionWithExternal deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionWithExternal.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionWithExternal.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(UnionWithExternal.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private UnionWithExternal deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "field" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new UnionWithExternal((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static UnionWithExternal deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionWithExternal.class, "Expected the end of a JSON object while deserializing a union"); + } + return new UnionWithExternal(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/jakartaservice/com/palantir/product/datasets/BackingFileSystem.java b/conjure-java-core/src/integrationInput/java/jakartaservice/com/palantir/product/datasets/BackingFileSystem.java index e037d1118..ec19ae49d 100644 --- a/conjure-java-core/src/integrationInput/java/jakartaservice/com/palantir/product/datasets/BackingFileSystem.java +++ b/conjure-java-core/src/integrationInput/java/jakartaservice/com/palantir/product/datasets/BackingFileSystem.java @@ -131,6 +131,7 @@ public static final class Builder { private String baseUri; + @JsonSetter(value = "configuration", nulls = Nulls.SKIP) private Map configuration = new LinkedHashMap<>(); private Builder() {} @@ -158,7 +159,6 @@ public Builder baseUri(@Nonnull String baseUri) { return this; } - @JsonSetter(value = "configuration", nulls = Nulls.SKIP) public Builder configuration(@Nonnull Map configuration) { checkNotBuilt(); this.configuration = diff --git a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java index c25dbc7de..e85d3b96a 100644 --- a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java @@ -6,15 +6,24 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,6 +34,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = SimpleUnion.Deserializer.class) public final class SimpleUnion { private final Base value; @@ -206,23 +216,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(FooWrapper.class), - @JsonSubTypes.Type(BarWrapper.class), - @JsonSubTypes.Type(BazWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("foo") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class FooWrapper implements Base { private final String value; @@ -268,6 +267,7 @@ public String toString() { } @JsonTypeName("bar") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BarWrapper implements Base { private final int value; @@ -313,6 +313,7 @@ public String toString() { } @JsonTypeName("baz") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BazWrapper implements Base { private final SafeLong value; @@ -416,4 +417,129 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = + new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "foo" -> 0; + case "bar" -> 1; + case "baz" -> 2; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new SimpleUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new SimpleUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/datasets/BackingFileSystem.java b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/datasets/BackingFileSystem.java index 87b1ce56c..6c4dedbc0 100644 --- a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/datasets/BackingFileSystem.java +++ b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/datasets/BackingFileSystem.java @@ -131,6 +131,7 @@ public static final class Builder { private String baseUri; + @JsonSetter(value = "configuration", nulls = Nulls.SKIP) private Map configuration = new LinkedHashMap<>(); private Builder() {} @@ -158,7 +159,6 @@ public Builder baseUri(@Nonnull String baseUri) { return this; } - @JsonSetter(value = "configuration", nulls = Nulls.SKIP) public Builder configuration(@Nonnull Map configuration) { checkNotBuilt(); this.configuration = diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java index 5189f8585..1b2a1d942 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java @@ -6,13 +6,26 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -23,13 +36,8 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = CamelCaseUnion.Unknown.class) -@JsonSubTypes(@JsonSubTypes.Type(value = CamelCaseUnion.CamelCasedField.class, name = "camelCasedField")) +@JsonDeserialize(using = CamelCaseUnion.Deserializer.class) +@JsonSerialize(using = CamelCaseUnion.Serializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class CamelCaseUnion permits CamelCaseUnion.CamelCasedField, CamelCaseUnion.Unknown { public static CamelCaseUnion camelCasedField(String value) { @@ -61,6 +69,9 @@ public Known throwOnUnknown() { public sealed interface Known permits CamelCasedField {} @JsonTypeName("camelCasedField") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class CamelCasedField extends CamelCaseUnion implements Known { private final String value; @@ -105,6 +116,8 @@ public String toString() { } } + @JsonDeserialize + @JsonSerialize public static final class Unknown extends CamelCaseUnion { private final String type; @@ -165,6 +178,137 @@ public String toString() { } } + static final class Serializer extends JsonSerializer { + @Override + public void serialize(CamelCaseUnion value, JsonGenerator generator, SerializerProvider serializers) + throws IOException { + serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); + } + } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {CamelCasedField.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public CamelCaseUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + CamelCaseUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + CamelCaseUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private CamelCaseUnion deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + CamelCaseUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + CamelCaseUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(CamelCaseUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private CamelCaseUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "camelCasedField" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return (CamelCaseUnion) deserializer.deserialize(parser, context); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static CamelCaseUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + CamelCaseUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Unknown(type, values); + } + } + public interface Visitor { T visitCamelCasedField(String value); diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java index 8fc7dc5d0..ae28faf6a 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java @@ -5,12 +5,25 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -21,13 +34,8 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = EmptyUnion.Unknown.class) -@JsonSubTypes({}) +@JsonDeserialize(using = EmptyUnion.Deserializer.class) +@JsonSerialize(using = EmptyUnion.Serializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class EmptyUnion permits EmptyUnion.Unknown { public static EmptyUnion unknown(@Safe String type, Object value) { @@ -39,6 +47,8 @@ public static EmptyUnion unknown(@Safe String type, Object value) { public abstract T accept(Visitor visitor); + @JsonDeserialize + @JsonSerialize public static final class Unknown extends EmptyUnion { private final String type; @@ -99,6 +109,134 @@ public String toString() { } } + static final class Serializer extends JsonSerializer { + @Override + public void serialize(EmptyUnion value, JsonGenerator generator, SerializerProvider serializers) + throws IOException { + serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); + } + } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + EmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return (EmptyUnion) deserializer.deserialize(parser, context); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Unknown(type, values); + } + } + public interface Visitor { T visitUnknown(@Safe String unknownType, Object unknownValue); diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java index 2e1bfd8c4..a27667a87 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java @@ -6,13 +6,26 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -23,13 +36,8 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = NestedEmptyUnion.Unknown.class) -@JsonSubTypes(@JsonSubTypes.Type(value = NestedEmptyUnion.Empty.class, name = "empty")) +@JsonDeserialize(using = NestedEmptyUnion.Deserializer.class) +@JsonSerialize(using = NestedEmptyUnion.Serializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class NestedEmptyUnion permits NestedEmptyUnion.Empty, NestedEmptyUnion.Unknown { public static NestedEmptyUnion empty(EmptyObject value) { @@ -61,6 +69,9 @@ public Known throwOnUnknown() { public sealed interface Known permits Empty {} @JsonTypeName("empty") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Empty extends NestedEmptyUnion implements Known { private final EmptyObject value; @@ -105,6 +116,8 @@ public String toString() { } } + @JsonDeserialize + @JsonSerialize public static final class Unknown extends NestedEmptyUnion { private final String type; @@ -165,6 +178,137 @@ public String toString() { } } + static final class Serializer extends JsonSerializer { + @Override + public void serialize(NestedEmptyUnion value, JsonGenerator generator, SerializerProvider serializers) + throws IOException { + serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); + } + } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {Empty.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public NestedEmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + NestedEmptyUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + NestedEmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private NestedEmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + NestedEmptyUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + NestedEmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(NestedEmptyUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private NestedEmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "empty" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return (NestedEmptyUnion) deserializer.deserialize(parser, context); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static NestedEmptyUnion deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + NestedEmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Unknown(type, values); + } + } + public interface Visitor { T visitEmpty(EmptyObject value); diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java index 7651f6410..96946ab20 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java @@ -6,14 +6,27 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,17 +38,8 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = SimpleUnion.Unknown.class) -@JsonSubTypes({ - @JsonSubTypes.Type(value = SimpleUnion.Foo.class, name = "foo"), - @JsonSubTypes.Type(value = SimpleUnion.Bar.class, name = "bar"), - @JsonSubTypes.Type(value = SimpleUnion.Baz.class, name = "baz") -}) +@JsonDeserialize(using = SimpleUnion.Deserializer.class) +@JsonSerialize(using = SimpleUnion.Serializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class SimpleUnion permits SimpleUnion.Foo, SimpleUnion.Bar, SimpleUnion.Baz, SimpleUnion.Unknown { @@ -81,6 +85,9 @@ public Known throwOnUnknown() { public sealed interface Known permits Foo, Bar, Baz {} @JsonTypeName("foo") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Foo extends SimpleUnion implements Known { private final String value; @@ -126,6 +133,9 @@ public String toString() { } @JsonTypeName("bar") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Bar extends SimpleUnion implements Known { private final int value; @@ -171,6 +181,9 @@ public String toString() { } @JsonTypeName("baz") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Baz extends SimpleUnion implements Known { private final SafeLong value; @@ -215,6 +228,8 @@ public String toString() { } } + @JsonDeserialize + @JsonSerialize public static final class Unknown extends SimpleUnion { private final String type; @@ -275,6 +290,138 @@ public String toString() { } } + static final class Serializer extends JsonSerializer { + @Override + public void serialize(SimpleUnion value, JsonGenerator generator, SerializerProvider serializers) + throws IOException { + serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); + } + } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {Foo.class, Bar.class, Baz.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "foo" -> 0; + case "bar" -> 1; + case "baz" -> 2; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return (SimpleUnion) deserializer.deserialize(parser, context); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Unknown(type, values); + } + } + public interface Visitor { T visitFoo(String value); diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java index 9d2c43e9b..5ad67c9c8 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java @@ -6,13 +6,26 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -23,29 +36,8 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnionReservedNames.Unknown.class) -@JsonSubTypes({ - @JsonSubTypes.Type(value = UnionReservedNames.Known_.class, name = "known"), - @JsonSubTypes.Type(value = UnionReservedNames.Unknown_.class, name = "unknown"), - @JsonSubTypes.Type(value = UnionReservedNames.If.class, name = "if"), - @JsonSubTypes.Type(value = UnionReservedNames.New.class, name = "new"), - @JsonSubTypes.Type(value = UnionReservedNames.Interface.class, name = "interface"), - @JsonSubTypes.Type(value = UnionReservedNames.Void.class, name = "void"), - @JsonSubTypes.Type(value = UnionReservedNames.Return.class, name = "return"), - @JsonSubTypes.Type(value = UnionReservedNames.Private.class, name = "private"), - @JsonSubTypes.Type(value = UnionReservedNames.Public.class, name = "public"), - @JsonSubTypes.Type(value = UnionReservedNames.Int.class, name = "int"), - @JsonSubTypes.Type(value = UnionReservedNames.Import.class, name = "import"), - @JsonSubTypes.Type(value = UnionReservedNames.Final.class, name = "final"), - @JsonSubTypes.Type(value = UnionReservedNames.Throws.class, name = "throws"), - @JsonSubTypes.Type(value = UnionReservedNames.Static.class, name = "static"), - @JsonSubTypes.Type(value = UnionReservedNames.UnionReservedNames_.class, name = "unionReservedNames") -}) +@JsonDeserialize(using = UnionReservedNames.Deserializer.class) +@JsonSerialize(using = UnionReservedNames.Serializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class UnionReservedNames permits UnionReservedNames.Known_, @@ -206,6 +198,9 @@ public sealed interface Known UnionReservedNames_ {} @JsonTypeName("known") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Known_ extends UnionReservedNames implements Known { private final String value; @@ -251,6 +246,9 @@ public String toString() { } @JsonTypeName("unknown") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Unknown_ extends UnionReservedNames implements Known { private final String value; @@ -296,6 +294,9 @@ public String toString() { } @JsonTypeName("if") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class If extends UnionReservedNames implements Known { private final String value; @@ -341,6 +342,9 @@ public String toString() { } @JsonTypeName("new") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class New extends UnionReservedNames implements Known { private final String value; @@ -386,6 +390,9 @@ public String toString() { } @JsonTypeName("interface") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Interface extends UnionReservedNames implements Known { private final String value; @@ -431,6 +438,9 @@ public String toString() { } @JsonTypeName("void") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Void extends UnionReservedNames implements Known { private final String value; @@ -476,6 +486,9 @@ public String toString() { } @JsonTypeName("return") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Return extends UnionReservedNames implements Known { private final String value; @@ -521,6 +534,9 @@ public String toString() { } @JsonTypeName("private") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Private extends UnionReservedNames implements Known { private final String value; @@ -566,6 +582,9 @@ public String toString() { } @JsonTypeName("public") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Public extends UnionReservedNames implements Known { private final String value; @@ -611,6 +630,9 @@ public String toString() { } @JsonTypeName("int") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Int extends UnionReservedNames implements Known { private final String value; @@ -656,6 +678,9 @@ public String toString() { } @JsonTypeName("import") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Import extends UnionReservedNames implements Known { private final String value; @@ -701,6 +726,9 @@ public String toString() { } @JsonTypeName("final") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Final extends UnionReservedNames implements Known { private final String value; @@ -746,6 +774,9 @@ public String toString() { } @JsonTypeName("throws") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Throws extends UnionReservedNames implements Known { private final String value; @@ -791,6 +822,9 @@ public String toString() { } @JsonTypeName("static") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class Static extends UnionReservedNames implements Known { private final String value; @@ -836,6 +870,9 @@ public String toString() { } @JsonTypeName("unionReservedNames") + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonDeserialize + @JsonSerialize public static final class UnionReservedNames_ extends UnionReservedNames implements Known { private final String value; @@ -880,6 +917,8 @@ public String toString() { } } + @JsonDeserialize + @JsonSerialize public static final class Unknown extends UnionReservedNames { private final String type; @@ -940,6 +979,167 @@ public String toString() { } } + static final class Serializer extends JsonSerializer { + @Override + public void serialize(UnionReservedNames value, JsonGenerator generator, SerializerProvider serializers) + throws IOException { + serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); + } + } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] { + Known_.class, + Unknown_.class, + If.class, + New.class, + Interface.class, + Void.class, + Return.class, + Private.class, + Public.class, + Int.class, + Import.class, + Final.class, + Throws.class, + Static.class, + UnionReservedNames_.class + }; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public UnionReservedNames deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + UnionReservedNames.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionReservedNames.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private UnionReservedNames deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionReservedNames.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionReservedNames.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(UnionReservedNames.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private UnionReservedNames deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "known" -> 0; + case "unknown" -> 1; + case "if" -> 2; + case "new" -> 3; + case "interface" -> 4; + case "void" -> 5; + case "return" -> 6; + case "private" -> 7; + case "public" -> 8; + case "int" -> 9; + case "import" -> 10; + case "final" -> 11; + case "throws" -> 12; + case "static" -> 13; + case "unionReservedNames" -> 14; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return (UnionReservedNames) deserializer.deserialize(parser, context); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static UnionReservedNames deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionReservedNames.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Unknown(type, values); + } + } + public interface Visitor { T visitKnown(String value); diff --git a/conjure-java-core/src/integrationInput/java/servicevanilla/com/palantir/product/datasets/BackingFileSystem.java b/conjure-java-core/src/integrationInput/java/servicevanilla/com/palantir/product/datasets/BackingFileSystem.java index 6765c2924..9010f0830 100644 --- a/conjure-java-core/src/integrationInput/java/servicevanilla/com/palantir/product/datasets/BackingFileSystem.java +++ b/conjure-java-core/src/integrationInput/java/servicevanilla/com/palantir/product/datasets/BackingFileSystem.java @@ -131,6 +131,7 @@ public static final class Builder { private String baseUri; + @JsonSetter(value = "configuration", nulls = Nulls.SKIP) private Map configuration = new LinkedHashMap<>(); private Builder() {} @@ -158,7 +159,6 @@ public Builder baseUri(@Nonnull String baseUri) { return this; } - @JsonSetter(value = "configuration", nulls = Nulls.SKIP) public Builder configuration(@Nonnull Map configuration) { checkNotBuilt(); this.configuration = diff --git a/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleFieldsOneFinalStage.java b/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleFieldsOneFinalStage.java index 23c19e716..2ea8dd384 100644 --- a/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleFieldsOneFinalStage.java +++ b/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleFieldsOneFinalStage.java @@ -195,6 +195,7 @@ public interface Builder extends TokenStageBuilder, Completed_StageBuilder { static final class DefaultBuilder implements Builder { boolean _buildInvoked; + @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) private Map mappedRids = new LinkedHashMap<>(); private OneField token; @@ -213,7 +214,6 @@ public Builder from(MultipleFieldsOneFinalStage other) { } @Override - @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) public Builder mappedRids(@Nonnull Map mappedRids) { checkNotBuilt(); this.mappedRids = new LinkedHashMap<>(Preconditions.checkNotNull(mappedRids, "mappedRids cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleFieldsOnlyFinalStage.java b/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleFieldsOnlyFinalStage.java index f41afeafb..4290183f3 100644 --- a/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleFieldsOnlyFinalStage.java +++ b/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleFieldsOnlyFinalStage.java @@ -207,6 +207,7 @@ public static final class Builder { private List items = ConjureCollections.newList(); + @JsonSetter(value = "itemsMap", nulls = Nulls.SKIP) private Map itemsMap = new LinkedHashMap<>(); private Optional optionalItem = Optional.empty(); @@ -215,6 +216,7 @@ public static final class Builder { private List itemsOld = ConjureCollections.newList(); + @JsonSetter(value = "itemsMapOld", nulls = Nulls.SKIP) private Map itemsMapOld = new LinkedHashMap<>(); private Optional optionalItemOld = Optional.empty(); @@ -255,7 +257,6 @@ public Builder items(String items) { return this; } - @JsonSetter(value = "itemsMap", nulls = Nulls.SKIP) public Builder itemsMap(@Nonnull Map itemsMap) { checkNotBuilt(); this.itemsMap = new LinkedHashMap<>(Preconditions.checkNotNull(itemsMap, "itemsMap cannot be null")); @@ -333,7 +334,6 @@ public Builder itemsOld(String itemsOld) { /** @deprecated this map is deprecated */ @Deprecated - @JsonSetter(value = "itemsMapOld", nulls = Nulls.SKIP) public Builder itemsMapOld(@Nonnull Map itemsMapOld) { checkNotBuilt(); this.itemsMapOld = diff --git a/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleOrderedStages.java b/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleOrderedStages.java index a1289bc69..c1a14270d 100644 --- a/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleOrderedStages.java +++ b/conjure-java-core/src/integrationInput/java/stagedbuilder/com/palantir/product/MultipleOrderedStages.java @@ -246,6 +246,7 @@ static final class DefaultBuilder implements Builder { private Set items = ConjureCollections.newSet(); + @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) private Map mappedRids = new LinkedHashMap<>(); private Optional optionalItem = Optional.empty(); @@ -302,7 +303,6 @@ public Builder items(SafeLong items) { } @Override - @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) public Builder mappedRids(@Nonnull Map mappedRids) { checkNotBuilt(); this.mappedRids = new LinkedHashMap<>(Preconditions.checkNotNull(mappedRids, "mappedRids cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/strictstagedbuilder/com/palantir/product/StrictFourFields.java b/conjure-java-core/src/integrationInput/java/strictstagedbuilder/com/palantir/product/StrictFourFields.java index 60420e4b2..8d8be3d06 100644 --- a/conjure-java-core/src/integrationInput/java/strictstagedbuilder/com/palantir/product/StrictFourFields.java +++ b/conjure-java-core/src/integrationInput/java/strictstagedbuilder/com/palantir/product/StrictFourFields.java @@ -206,6 +206,7 @@ static final class DefaultBuilder implements Builder { private Optional optionalItem = Optional.empty(); + @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) private Map mappedRids = new LinkedHashMap<>(); private DefaultBuilder() {} @@ -252,7 +253,6 @@ public Builder optionalItem(@Nonnull String optionalItem) { } @Override - @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) public Builder mappedRids(@Nonnull Map mappedRids) { checkNotBuilt(); this.mappedRids = new LinkedHashMap<>(Preconditions.checkNotNull(mappedRids, "mappedRids cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/strictstagedbuilder/com/palantir/product/StrictMultipleDeprecatedAndUnsafeFields.java b/conjure-java-core/src/integrationInput/java/strictstagedbuilder/com/palantir/product/StrictMultipleDeprecatedAndUnsafeFields.java index b426e42d9..bc58171fa 100644 --- a/conjure-java-core/src/integrationInput/java/strictstagedbuilder/com/palantir/product/StrictMultipleDeprecatedAndUnsafeFields.java +++ b/conjure-java-core/src/integrationInput/java/strictstagedbuilder/com/palantir/product/StrictMultipleDeprecatedAndUnsafeFields.java @@ -287,6 +287,7 @@ static final class DefaultBuilder implements Builder { private Optional<@Unsafe String> optionalItem = Optional.empty(); + @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) private Map mappedRids = new LinkedHashMap<>(); private StrictFourFields strictFourFieldsObject; @@ -365,7 +366,6 @@ public Builder optionalItem(@Nonnull @Unsafe String optionalItem) { } @Override - @JsonSetter(value = "mappedRids", nulls = Nulls.SKIP) public Builder mappedRids(@Nonnull Map mappedRids) { checkNotBuilt(); this.mappedRids = new LinkedHashMap<>(Preconditions.checkNotNull(mappedRids, "mappedRids cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/AliasAsMapKeyExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/AliasAsMapKeyExample.java index bdc4985da..0efbefb7e 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/AliasAsMapKeyExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/AliasAsMapKeyExample.java @@ -178,18 +178,25 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; + @JsonSetter(value = "strings", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map strings = new LinkedHashMap<>(); + @JsonSetter(value = "rids", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map rids = new LinkedHashMap<>(); + @JsonSetter(value = "bearertokens", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map bearertokens = new LinkedHashMap<>(); + @JsonSetter(value = "integers", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map integers = new LinkedHashMap<>(); + @JsonSetter(value = "safelongs", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map safelongs = new LinkedHashMap<>(); + @JsonSetter(value = "datetimes", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map datetimes = new LinkedHashMap<>(); + @JsonSetter(value = "uuids", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map uuids = new LinkedHashMap<>(); private Builder() {} @@ -206,7 +213,6 @@ public Builder from(AliasAsMapKeyExample other) { return this; } - @JsonSetter(value = "strings", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder strings(@Nonnull Map strings) { checkNotBuilt(); this.strings = new LinkedHashMap<>(Preconditions.checkNotNull(strings, "strings cannot be null")); @@ -225,7 +231,6 @@ public Builder strings(StringAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "rids", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder rids(@Nonnull Map rids) { checkNotBuilt(); this.rids = new LinkedHashMap<>(Preconditions.checkNotNull(rids, "rids cannot be null")); @@ -244,7 +249,6 @@ public Builder rids(RidAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "bearertokens", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder bearertokens(@Nonnull Map bearertokens) { checkNotBuilt(); this.bearertokens = @@ -264,7 +268,6 @@ public Builder bearertokens(BearerTokenAliasExample key, ManyFieldExample value) return this; } - @JsonSetter(value = "integers", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder integers(@Nonnull Map integers) { checkNotBuilt(); this.integers = new LinkedHashMap<>(Preconditions.checkNotNull(integers, "integers cannot be null")); @@ -283,7 +286,6 @@ public Builder integers(IntegerAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "safelongs", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder safelongs(@Nonnull Map safelongs) { checkNotBuilt(); this.safelongs = new LinkedHashMap<>(Preconditions.checkNotNull(safelongs, "safelongs cannot be null")); @@ -302,7 +304,6 @@ public Builder safelongs(SafeLongAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "datetimes", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder datetimes(@Nonnull Map datetimes) { checkNotBuilt(); this.datetimes = new LinkedHashMap<>(Preconditions.checkNotNull(datetimes, "datetimes cannot be null")); @@ -321,7 +322,6 @@ public Builder datetimes(DateTimeAliasExample key, ManyFieldExample value) { return this; } - @JsonSetter(value = "uuids", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder uuids(@Nonnull Map uuids) { checkNotBuilt(); this.uuids = new LinkedHashMap<>(Preconditions.checkNotNull(uuids, "uuids cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/AnyMapExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/AnyMapExample.java index 949df69be..22763acce 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/AnyMapExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/AnyMapExample.java @@ -91,6 +91,7 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; + @JsonSetter(value = "items", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map items = new LinkedHashMap<>(); private Builder() {} @@ -101,7 +102,6 @@ public Builder from(AnyMapExample other) { return this; } - @JsonSetter(value = "items", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder items(@Nonnull Map items) { checkNotBuilt(); this.items = new LinkedHashMap<>(Preconditions.checkNotNull(items, "items cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java index f15ae80de..9512fa137 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java @@ -3,14 +3,23 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -20,6 +29,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = EmptyUnionTypeExample.Deserializer.class) public final class EmptyUnionTypeExample { private final Base value; @@ -113,13 +123,6 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @@ -183,4 +186,127 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public EmptyUnionTypeExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private EmptyUnionTypeExample deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(EmptyUnionTypeExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private EmptyUnionTypeExample deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new EmptyUnionTypeExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static EmptyUnionTypeExample deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + EmptyUnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + } + return new EmptyUnionTypeExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java index 05779ce06..6e2d94371 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java @@ -6,14 +6,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -24,6 +33,7 @@ /** A union of a safe long. */ @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = ExternalLongUnionExample.Deserializer.class) public final class ExternalLongUnionExample { private final Base value; @@ -147,19 +157,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes(@JsonSubTypes.Type(SafeLongWrapper.class)) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("safeLong") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SafeLongWrapper implements Base { private final long value; @@ -263,4 +266,132 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer + implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {SafeLongWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public ExternalLongUnionExample deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private ExternalLongUnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch( + ExternalLongUnionExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private ExternalLongUnionExample deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "safeLong" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new ExternalLongUnionExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static ExternalLongUnionExample deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + ExternalLongUnionExample.class, + "Expected the end of a JSON object while deserializing a union"); + } + return new ExternalLongUnionExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java index d8060a480..d44851b16 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java @@ -6,13 +6,22 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -21,6 +30,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = LargeUnionExample.Deserializer.class) public final class LargeUnionExample { private final Base value; @@ -977,121 +987,12 @@ public interface Visitor { T visitUnknown(@Safe String unknownType); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(U0Wrapper.class), - @JsonSubTypes.Type(U1Wrapper.class), - @JsonSubTypes.Type(U2Wrapper.class), - @JsonSubTypes.Type(U3Wrapper.class), - @JsonSubTypes.Type(U4Wrapper.class), - @JsonSubTypes.Type(U5Wrapper.class), - @JsonSubTypes.Type(U6Wrapper.class), - @JsonSubTypes.Type(U7Wrapper.class), - @JsonSubTypes.Type(U8Wrapper.class), - @JsonSubTypes.Type(U9Wrapper.class), - @JsonSubTypes.Type(U10Wrapper.class), - @JsonSubTypes.Type(U11Wrapper.class), - @JsonSubTypes.Type(U12Wrapper.class), - @JsonSubTypes.Type(U13Wrapper.class), - @JsonSubTypes.Type(U14Wrapper.class), - @JsonSubTypes.Type(U15Wrapper.class), - @JsonSubTypes.Type(U16Wrapper.class), - @JsonSubTypes.Type(U17Wrapper.class), - @JsonSubTypes.Type(U18Wrapper.class), - @JsonSubTypes.Type(U19Wrapper.class), - @JsonSubTypes.Type(U20Wrapper.class), - @JsonSubTypes.Type(U21Wrapper.class), - @JsonSubTypes.Type(U22Wrapper.class), - @JsonSubTypes.Type(U23Wrapper.class), - @JsonSubTypes.Type(U24Wrapper.class), - @JsonSubTypes.Type(U25Wrapper.class), - @JsonSubTypes.Type(U26Wrapper.class), - @JsonSubTypes.Type(U27Wrapper.class), - @JsonSubTypes.Type(U28Wrapper.class), - @JsonSubTypes.Type(U29Wrapper.class), - @JsonSubTypes.Type(U30Wrapper.class), - @JsonSubTypes.Type(U31Wrapper.class), - @JsonSubTypes.Type(U32Wrapper.class), - @JsonSubTypes.Type(U33Wrapper.class), - @JsonSubTypes.Type(U34Wrapper.class), - @JsonSubTypes.Type(U35Wrapper.class), - @JsonSubTypes.Type(U36Wrapper.class), - @JsonSubTypes.Type(U37Wrapper.class), - @JsonSubTypes.Type(U38Wrapper.class), - @JsonSubTypes.Type(U39Wrapper.class), - @JsonSubTypes.Type(U40Wrapper.class), - @JsonSubTypes.Type(U41Wrapper.class), - @JsonSubTypes.Type(U42Wrapper.class), - @JsonSubTypes.Type(U43Wrapper.class), - @JsonSubTypes.Type(U44Wrapper.class), - @JsonSubTypes.Type(U45Wrapper.class), - @JsonSubTypes.Type(U46Wrapper.class), - @JsonSubTypes.Type(U47Wrapper.class), - @JsonSubTypes.Type(U48Wrapper.class), - @JsonSubTypes.Type(U49Wrapper.class), - @JsonSubTypes.Type(U50Wrapper.class), - @JsonSubTypes.Type(U51Wrapper.class), - @JsonSubTypes.Type(U52Wrapper.class), - @JsonSubTypes.Type(U53Wrapper.class), - @JsonSubTypes.Type(U54Wrapper.class), - @JsonSubTypes.Type(U55Wrapper.class), - @JsonSubTypes.Type(U56Wrapper.class), - @JsonSubTypes.Type(U57Wrapper.class), - @JsonSubTypes.Type(U58Wrapper.class), - @JsonSubTypes.Type(U59Wrapper.class), - @JsonSubTypes.Type(U60Wrapper.class), - @JsonSubTypes.Type(U61Wrapper.class), - @JsonSubTypes.Type(U62Wrapper.class), - @JsonSubTypes.Type(U63Wrapper.class), - @JsonSubTypes.Type(U64Wrapper.class), - @JsonSubTypes.Type(U65Wrapper.class), - @JsonSubTypes.Type(U66Wrapper.class), - @JsonSubTypes.Type(U67Wrapper.class), - @JsonSubTypes.Type(U68Wrapper.class), - @JsonSubTypes.Type(U69Wrapper.class), - @JsonSubTypes.Type(U70Wrapper.class), - @JsonSubTypes.Type(U71Wrapper.class), - @JsonSubTypes.Type(U72Wrapper.class), - @JsonSubTypes.Type(U73Wrapper.class), - @JsonSubTypes.Type(U74Wrapper.class), - @JsonSubTypes.Type(U75Wrapper.class), - @JsonSubTypes.Type(U76Wrapper.class), - @JsonSubTypes.Type(U77Wrapper.class), - @JsonSubTypes.Type(U78Wrapper.class), - @JsonSubTypes.Type(U79Wrapper.class), - @JsonSubTypes.Type(U80Wrapper.class), - @JsonSubTypes.Type(U81Wrapper.class), - @JsonSubTypes.Type(U82Wrapper.class), - @JsonSubTypes.Type(U83Wrapper.class), - @JsonSubTypes.Type(U84Wrapper.class), - @JsonSubTypes.Type(U85Wrapper.class), - @JsonSubTypes.Type(U86Wrapper.class), - @JsonSubTypes.Type(U87Wrapper.class), - @JsonSubTypes.Type(U88Wrapper.class), - @JsonSubTypes.Type(U89Wrapper.class), - @JsonSubTypes.Type(U90Wrapper.class), - @JsonSubTypes.Type(U91Wrapper.class), - @JsonSubTypes.Type(U92Wrapper.class), - @JsonSubTypes.Type(U93Wrapper.class), - @JsonSubTypes.Type(U94Wrapper.class), - @JsonSubTypes.Type(U95Wrapper.class), - @JsonSubTypes.Type(U96Wrapper.class), - @JsonSubTypes.Type(U97Wrapper.class), - @JsonSubTypes.Type(U98Wrapper.class), - @JsonSubTypes.Type(U99Wrapper.class), - @JsonSubTypes.Type(U100Wrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("u0") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U0Wrapper implements Base { private final String value; @@ -1137,6 +1038,7 @@ public String toString() { } @JsonTypeName("u1") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U1Wrapper implements Base { private final String value; @@ -1182,6 +1084,7 @@ public String toString() { } @JsonTypeName("u2") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U2Wrapper implements Base { private final String value; @@ -1227,6 +1130,7 @@ public String toString() { } @JsonTypeName("u3") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U3Wrapper implements Base { private final String value; @@ -1272,6 +1176,7 @@ public String toString() { } @JsonTypeName("u4") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U4Wrapper implements Base { private final String value; @@ -1317,6 +1222,7 @@ public String toString() { } @JsonTypeName("u5") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U5Wrapper implements Base { private final String value; @@ -1362,6 +1268,7 @@ public String toString() { } @JsonTypeName("u6") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U6Wrapper implements Base { private final String value; @@ -1407,6 +1314,7 @@ public String toString() { } @JsonTypeName("u7") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U7Wrapper implements Base { private final String value; @@ -1452,6 +1360,7 @@ public String toString() { } @JsonTypeName("u8") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U8Wrapper implements Base { private final String value; @@ -1497,6 +1406,7 @@ public String toString() { } @JsonTypeName("u9") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U9Wrapper implements Base { private final String value; @@ -1542,6 +1452,7 @@ public String toString() { } @JsonTypeName("u10") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U10Wrapper implements Base { private final String value; @@ -1587,6 +1498,7 @@ public String toString() { } @JsonTypeName("u11") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U11Wrapper implements Base { private final String value; @@ -1632,6 +1544,7 @@ public String toString() { } @JsonTypeName("u12") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U12Wrapper implements Base { private final String value; @@ -1677,6 +1590,7 @@ public String toString() { } @JsonTypeName("u13") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U13Wrapper implements Base { private final String value; @@ -1722,6 +1636,7 @@ public String toString() { } @JsonTypeName("u14") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U14Wrapper implements Base { private final String value; @@ -1767,6 +1682,7 @@ public String toString() { } @JsonTypeName("u15") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U15Wrapper implements Base { private final String value; @@ -1812,6 +1728,7 @@ public String toString() { } @JsonTypeName("u16") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U16Wrapper implements Base { private final String value; @@ -1857,6 +1774,7 @@ public String toString() { } @JsonTypeName("u17") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U17Wrapper implements Base { private final String value; @@ -1902,6 +1820,7 @@ public String toString() { } @JsonTypeName("u18") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U18Wrapper implements Base { private final String value; @@ -1947,6 +1866,7 @@ public String toString() { } @JsonTypeName("u19") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U19Wrapper implements Base { private final String value; @@ -1992,6 +1912,7 @@ public String toString() { } @JsonTypeName("u20") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U20Wrapper implements Base { private final String value; @@ -2037,6 +1958,7 @@ public String toString() { } @JsonTypeName("u21") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U21Wrapper implements Base { private final String value; @@ -2082,6 +2004,7 @@ public String toString() { } @JsonTypeName("u22") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U22Wrapper implements Base { private final String value; @@ -2127,6 +2050,7 @@ public String toString() { } @JsonTypeName("u23") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U23Wrapper implements Base { private final String value; @@ -2172,6 +2096,7 @@ public String toString() { } @JsonTypeName("u24") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U24Wrapper implements Base { private final String value; @@ -2217,6 +2142,7 @@ public String toString() { } @JsonTypeName("u25") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U25Wrapper implements Base { private final String value; @@ -2262,6 +2188,7 @@ public String toString() { } @JsonTypeName("u26") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U26Wrapper implements Base { private final String value; @@ -2307,6 +2234,7 @@ public String toString() { } @JsonTypeName("u27") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U27Wrapper implements Base { private final String value; @@ -2352,6 +2280,7 @@ public String toString() { } @JsonTypeName("u28") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U28Wrapper implements Base { private final String value; @@ -2397,6 +2326,7 @@ public String toString() { } @JsonTypeName("u29") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U29Wrapper implements Base { private final String value; @@ -2442,6 +2372,7 @@ public String toString() { } @JsonTypeName("u30") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U30Wrapper implements Base { private final String value; @@ -2487,6 +2418,7 @@ public String toString() { } @JsonTypeName("u31") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U31Wrapper implements Base { private final String value; @@ -2532,6 +2464,7 @@ public String toString() { } @JsonTypeName("u32") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U32Wrapper implements Base { private final String value; @@ -2577,6 +2510,7 @@ public String toString() { } @JsonTypeName("u33") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U33Wrapper implements Base { private final String value; @@ -2622,6 +2556,7 @@ public String toString() { } @JsonTypeName("u34") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U34Wrapper implements Base { private final String value; @@ -2667,6 +2602,7 @@ public String toString() { } @JsonTypeName("u35") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U35Wrapper implements Base { private final String value; @@ -2712,6 +2648,7 @@ public String toString() { } @JsonTypeName("u36") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U36Wrapper implements Base { private final String value; @@ -2757,6 +2694,7 @@ public String toString() { } @JsonTypeName("u37") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U37Wrapper implements Base { private final String value; @@ -2802,6 +2740,7 @@ public String toString() { } @JsonTypeName("u38") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U38Wrapper implements Base { private final String value; @@ -2847,6 +2786,7 @@ public String toString() { } @JsonTypeName("u39") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U39Wrapper implements Base { private final String value; @@ -2892,6 +2832,7 @@ public String toString() { } @JsonTypeName("u40") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U40Wrapper implements Base { private final String value; @@ -2937,6 +2878,7 @@ public String toString() { } @JsonTypeName("u41") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U41Wrapper implements Base { private final String value; @@ -2982,6 +2924,7 @@ public String toString() { } @JsonTypeName("u42") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U42Wrapper implements Base { private final String value; @@ -3027,6 +2970,7 @@ public String toString() { } @JsonTypeName("u43") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U43Wrapper implements Base { private final String value; @@ -3072,6 +3016,7 @@ public String toString() { } @JsonTypeName("u44") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U44Wrapper implements Base { private final String value; @@ -3117,6 +3062,7 @@ public String toString() { } @JsonTypeName("u45") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U45Wrapper implements Base { private final String value; @@ -3162,6 +3108,7 @@ public String toString() { } @JsonTypeName("u46") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U46Wrapper implements Base { private final String value; @@ -3207,6 +3154,7 @@ public String toString() { } @JsonTypeName("u47") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U47Wrapper implements Base { private final String value; @@ -3252,6 +3200,7 @@ public String toString() { } @JsonTypeName("u48") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U48Wrapper implements Base { private final String value; @@ -3297,6 +3246,7 @@ public String toString() { } @JsonTypeName("u49") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U49Wrapper implements Base { private final String value; @@ -3342,6 +3292,7 @@ public String toString() { } @JsonTypeName("u50") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U50Wrapper implements Base { private final String value; @@ -3387,6 +3338,7 @@ public String toString() { } @JsonTypeName("u51") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U51Wrapper implements Base { private final String value; @@ -3432,6 +3384,7 @@ public String toString() { } @JsonTypeName("u52") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U52Wrapper implements Base { private final String value; @@ -3477,6 +3430,7 @@ public String toString() { } @JsonTypeName("u53") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U53Wrapper implements Base { private final String value; @@ -3522,6 +3476,7 @@ public String toString() { } @JsonTypeName("u54") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U54Wrapper implements Base { private final String value; @@ -3567,6 +3522,7 @@ public String toString() { } @JsonTypeName("u55") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U55Wrapper implements Base { private final String value; @@ -3612,6 +3568,7 @@ public String toString() { } @JsonTypeName("u56") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U56Wrapper implements Base { private final String value; @@ -3657,6 +3614,7 @@ public String toString() { } @JsonTypeName("u57") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U57Wrapper implements Base { private final String value; @@ -3702,6 +3660,7 @@ public String toString() { } @JsonTypeName("u58") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U58Wrapper implements Base { private final String value; @@ -3747,6 +3706,7 @@ public String toString() { } @JsonTypeName("u59") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U59Wrapper implements Base { private final String value; @@ -3792,6 +3752,7 @@ public String toString() { } @JsonTypeName("u60") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U60Wrapper implements Base { private final String value; @@ -3837,6 +3798,7 @@ public String toString() { } @JsonTypeName("u61") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U61Wrapper implements Base { private final String value; @@ -3882,6 +3844,7 @@ public String toString() { } @JsonTypeName("u62") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U62Wrapper implements Base { private final String value; @@ -3927,6 +3890,7 @@ public String toString() { } @JsonTypeName("u63") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U63Wrapper implements Base { private final String value; @@ -3972,6 +3936,7 @@ public String toString() { } @JsonTypeName("u64") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U64Wrapper implements Base { private final String value; @@ -4017,6 +3982,7 @@ public String toString() { } @JsonTypeName("u65") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U65Wrapper implements Base { private final String value; @@ -4062,6 +4028,7 @@ public String toString() { } @JsonTypeName("u66") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U66Wrapper implements Base { private final String value; @@ -4107,6 +4074,7 @@ public String toString() { } @JsonTypeName("u67") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U67Wrapper implements Base { private final String value; @@ -4152,6 +4120,7 @@ public String toString() { } @JsonTypeName("u68") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U68Wrapper implements Base { private final String value; @@ -4197,6 +4166,7 @@ public String toString() { } @JsonTypeName("u69") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U69Wrapper implements Base { private final String value; @@ -4242,6 +4212,7 @@ public String toString() { } @JsonTypeName("u70") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U70Wrapper implements Base { private final String value; @@ -4287,6 +4258,7 @@ public String toString() { } @JsonTypeName("u71") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U71Wrapper implements Base { private final String value; @@ -4332,6 +4304,7 @@ public String toString() { } @JsonTypeName("u72") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U72Wrapper implements Base { private final String value; @@ -4377,6 +4350,7 @@ public String toString() { } @JsonTypeName("u73") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U73Wrapper implements Base { private final String value; @@ -4422,6 +4396,7 @@ public String toString() { } @JsonTypeName("u74") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U74Wrapper implements Base { private final String value; @@ -4467,6 +4442,7 @@ public String toString() { } @JsonTypeName("u75") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U75Wrapper implements Base { private final String value; @@ -4512,6 +4488,7 @@ public String toString() { } @JsonTypeName("u76") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U76Wrapper implements Base { private final String value; @@ -4557,6 +4534,7 @@ public String toString() { } @JsonTypeName("u77") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U77Wrapper implements Base { private final String value; @@ -4602,6 +4580,7 @@ public String toString() { } @JsonTypeName("u78") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U78Wrapper implements Base { private final String value; @@ -4647,6 +4626,7 @@ public String toString() { } @JsonTypeName("u79") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U79Wrapper implements Base { private final String value; @@ -4692,6 +4672,7 @@ public String toString() { } @JsonTypeName("u80") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U80Wrapper implements Base { private final String value; @@ -4737,6 +4718,7 @@ public String toString() { } @JsonTypeName("u81") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U81Wrapper implements Base { private final String value; @@ -4782,6 +4764,7 @@ public String toString() { } @JsonTypeName("u82") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U82Wrapper implements Base { private final String value; @@ -4827,6 +4810,7 @@ public String toString() { } @JsonTypeName("u83") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U83Wrapper implements Base { private final String value; @@ -4872,6 +4856,7 @@ public String toString() { } @JsonTypeName("u84") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U84Wrapper implements Base { private final String value; @@ -4917,6 +4902,7 @@ public String toString() { } @JsonTypeName("u85") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U85Wrapper implements Base { private final String value; @@ -4962,6 +4948,7 @@ public String toString() { } @JsonTypeName("u86") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U86Wrapper implements Base { private final String value; @@ -5007,6 +4994,7 @@ public String toString() { } @JsonTypeName("u87") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U87Wrapper implements Base { private final String value; @@ -5052,6 +5040,7 @@ public String toString() { } @JsonTypeName("u88") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U88Wrapper implements Base { private final String value; @@ -5097,6 +5086,7 @@ public String toString() { } @JsonTypeName("u89") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U89Wrapper implements Base { private final String value; @@ -5142,6 +5132,7 @@ public String toString() { } @JsonTypeName("u90") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U90Wrapper implements Base { private final String value; @@ -5187,6 +5178,7 @@ public String toString() { } @JsonTypeName("u91") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U91Wrapper implements Base { private final String value; @@ -5232,6 +5224,7 @@ public String toString() { } @JsonTypeName("u92") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U92Wrapper implements Base { private final String value; @@ -5277,6 +5270,7 @@ public String toString() { } @JsonTypeName("u93") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U93Wrapper implements Base { private final String value; @@ -5322,6 +5316,7 @@ public String toString() { } @JsonTypeName("u94") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U94Wrapper implements Base { private final String value; @@ -5367,6 +5362,7 @@ public String toString() { } @JsonTypeName("u95") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U95Wrapper implements Base { private final String value; @@ -5412,6 +5408,7 @@ public String toString() { } @JsonTypeName("u96") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U96Wrapper implements Base { private final String value; @@ -5457,6 +5454,7 @@ public String toString() { } @JsonTypeName("u97") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U97Wrapper implements Base { private final String value; @@ -5502,6 +5500,7 @@ public String toString() { } @JsonTypeName("u98") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U98Wrapper implements Base { private final String value; @@ -5547,6 +5546,7 @@ public String toString() { } @JsonTypeName("u99") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U99Wrapper implements Base { private final String value; @@ -5592,6 +5592,7 @@ public String toString() { } @JsonTypeName("u100") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class U100Wrapper implements Base { private final String value; @@ -5695,4 +5696,329 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] { + U0Wrapper.class, + U1Wrapper.class, + U2Wrapper.class, + U3Wrapper.class, + U4Wrapper.class, + U5Wrapper.class, + U6Wrapper.class, + U7Wrapper.class, + U8Wrapper.class, + U9Wrapper.class, + U10Wrapper.class, + U11Wrapper.class, + U12Wrapper.class, + U13Wrapper.class, + U14Wrapper.class, + U15Wrapper.class, + U16Wrapper.class, + U17Wrapper.class, + U18Wrapper.class, + U19Wrapper.class, + U20Wrapper.class, + U21Wrapper.class, + U22Wrapper.class, + U23Wrapper.class, + U24Wrapper.class, + U25Wrapper.class, + U26Wrapper.class, + U27Wrapper.class, + U28Wrapper.class, + U29Wrapper.class, + U30Wrapper.class, + U31Wrapper.class, + U32Wrapper.class, + U33Wrapper.class, + U34Wrapper.class, + U35Wrapper.class, + U36Wrapper.class, + U37Wrapper.class, + U38Wrapper.class, + U39Wrapper.class, + U40Wrapper.class, + U41Wrapper.class, + U42Wrapper.class, + U43Wrapper.class, + U44Wrapper.class, + U45Wrapper.class, + U46Wrapper.class, + U47Wrapper.class, + U48Wrapper.class, + U49Wrapper.class, + U50Wrapper.class, + U51Wrapper.class, + U52Wrapper.class, + U53Wrapper.class, + U54Wrapper.class, + U55Wrapper.class, + U56Wrapper.class, + U57Wrapper.class, + U58Wrapper.class, + U59Wrapper.class, + U60Wrapper.class, + U61Wrapper.class, + U62Wrapper.class, + U63Wrapper.class, + U64Wrapper.class, + U65Wrapper.class, + U66Wrapper.class, + U67Wrapper.class, + U68Wrapper.class, + U69Wrapper.class, + U70Wrapper.class, + U71Wrapper.class, + U72Wrapper.class, + U73Wrapper.class, + U74Wrapper.class, + U75Wrapper.class, + U76Wrapper.class, + U77Wrapper.class, + U78Wrapper.class, + U79Wrapper.class, + U80Wrapper.class, + U81Wrapper.class, + U82Wrapper.class, + U83Wrapper.class, + U84Wrapper.class, + U85Wrapper.class, + U86Wrapper.class, + U87Wrapper.class, + U88Wrapper.class, + U89Wrapper.class, + U90Wrapper.class, + U91Wrapper.class, + U92Wrapper.class, + U93Wrapper.class, + U94Wrapper.class, + U95Wrapper.class, + U96Wrapper.class, + U97Wrapper.class, + U98Wrapper.class, + U99Wrapper.class, + U100Wrapper.class + }; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public LargeUnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + LargeUnionExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + LargeUnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private LargeUnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + LargeUnionExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + LargeUnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(LargeUnionExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private LargeUnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "u0" -> 0; + case "u1" -> 1; + case "u2" -> 2; + case "u3" -> 3; + case "u4" -> 4; + case "u5" -> 5; + case "u6" -> 6; + case "u7" -> 7; + case "u8" -> 8; + case "u9" -> 9; + case "u10" -> 10; + case "u11" -> 11; + case "u12" -> 12; + case "u13" -> 13; + case "u14" -> 14; + case "u15" -> 15; + case "u16" -> 16; + case "u17" -> 17; + case "u18" -> 18; + case "u19" -> 19; + case "u20" -> 20; + case "u21" -> 21; + case "u22" -> 22; + case "u23" -> 23; + case "u24" -> 24; + case "u25" -> 25; + case "u26" -> 26; + case "u27" -> 27; + case "u28" -> 28; + case "u29" -> 29; + case "u30" -> 30; + case "u31" -> 31; + case "u32" -> 32; + case "u33" -> 33; + case "u34" -> 34; + case "u35" -> 35; + case "u36" -> 36; + case "u37" -> 37; + case "u38" -> 38; + case "u39" -> 39; + case "u40" -> 40; + case "u41" -> 41; + case "u42" -> 42; + case "u43" -> 43; + case "u44" -> 44; + case "u45" -> 45; + case "u46" -> 46; + case "u47" -> 47; + case "u48" -> 48; + case "u49" -> 49; + case "u50" -> 50; + case "u51" -> 51; + case "u52" -> 52; + case "u53" -> 53; + case "u54" -> 54; + case "u55" -> 55; + case "u56" -> 56; + case "u57" -> 57; + case "u58" -> 58; + case "u59" -> 59; + case "u60" -> 60; + case "u61" -> 61; + case "u62" -> 62; + case "u63" -> 63; + case "u64" -> 64; + case "u65" -> 65; + case "u66" -> 66; + case "u67" -> 67; + case "u68" -> 68; + case "u69" -> 69; + case "u70" -> 70; + case "u71" -> 71; + case "u72" -> 72; + case "u73" -> 73; + case "u74" -> 74; + case "u75" -> 75; + case "u76" -> 76; + case "u77" -> 77; + case "u78" -> 78; + case "u79" -> 79; + case "u80" -> 80; + case "u81" -> 81; + case "u82" -> 82; + case "u83" -> 83; + case "u84" -> 84; + case "u85" -> 85; + case "u86" -> 86; + case "u87" -> 87; + case "u88" -> 88; + case "u89" -> 89; + case "u90" -> 90; + case "u91" -> 91; + case "u92" -> 92; + case "u93" -> 93; + case "u94" -> 94; + case "u95" -> 95; + case "u96" -> 96; + case "u97" -> 97; + case "u98" -> 98; + case "u99" -> 99; + case "u100" -> 100; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new LargeUnionExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static LargeUnionExample deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + LargeUnionExample.class, "Expected the end of a JSON object while deserializing a union"); + } + return new LargeUnionExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ManyFieldExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ManyFieldExample.java index c17179657..782b04f5a 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ManyFieldExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ManyFieldExample.java @@ -372,6 +372,7 @@ static final class DefaultBuilder implements Builder { private Set set = ConjureCollections.newNonNullSet(); + @JsonSetter(value = "map", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map map = new LinkedHashMap<>(); private StringAliasExample alias; @@ -508,7 +509,6 @@ public Builder set(String set) { /** @deprecated deprecation documentation. */ @Deprecated @Override - @JsonSetter(value = "map", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder map(@Nonnull Map map) { checkNotBuilt(); this.map = new LinkedHashMap<>(Preconditions.checkNotNull(map, "map cannot be null")); diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/MapExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/MapExample.java index cb6c744a1..c854d2ce2 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/MapExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/MapExample.java @@ -123,10 +123,13 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; + @JsonSetter(value = "items", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) private Map items = new LinkedHashMap<>(); + @JsonSetter(value = "optionalItems", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) private Map> optionalItems = new LinkedHashMap<>(); + @JsonSetter(value = "aliasOptionalItems", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) private Map aliasOptionalItems = new LinkedHashMap<>(); private Builder() {} @@ -139,7 +142,6 @@ public Builder from(MapExample other) { return this; } - @JsonSetter(value = "items", nulls = Nulls.SKIP, contentNulls = Nulls.FAIL) public Builder items(@Nonnull Map items) { checkNotBuilt(); this.items = new LinkedHashMap<>(Preconditions.checkNotNull(items, "items cannot be null")); @@ -158,7 +160,6 @@ public Builder items(String key, String value) { return this; } - @JsonSetter(value = "optionalItems", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) public Builder optionalItems(@Nonnull Map> optionalItems) { checkNotBuilt(); this.optionalItems = @@ -178,7 +179,6 @@ public Builder optionalItems(String key, Optional value) { return this; } - @JsonSetter(value = "aliasOptionalItems", nulls = Nulls.SKIP, contentNulls = Nulls.AS_EMPTY) public Builder aliasOptionalItems(@Nonnull Map aliasOptionalItems) { checkNotBuilt(); this.aliasOptionalItems = new LinkedHashMap<>( diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java index 7ad4d3f80..f464f59a8 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java @@ -6,14 +6,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -23,6 +32,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = SingleUnion.Deserializer.class) public final class SingleUnion { private final Base value; @@ -144,19 +154,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes(@JsonSubTypes.Type(FooWrapper.class)) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("foo") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class FooWrapper implements Base { private final String value; @@ -260,4 +263,126 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public SingleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + SingleUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SingleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private SingleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SingleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SingleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(SingleUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private SingleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "foo" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new SingleUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static SingleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SingleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new SingleUnion(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java index 00da4ffc7..215a0b8f3 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java @@ -6,14 +6,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -24,6 +33,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = Union.Deserializer.class) public final class Union { private final Base value; @@ -221,23 +231,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(FooWrapper.class), - @JsonSubTypes.Type(BarWrapper.class), - @JsonSubTypes.Type(BazWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("foo") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class FooWrapper implements Base { private final String value; @@ -283,6 +282,7 @@ public String toString() { } @JsonTypeName("bar") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BarWrapper implements Base { private final int value; @@ -329,6 +329,7 @@ public String toString() { } @JsonTypeName("baz") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BazWrapper implements Base { private final long value; @@ -433,4 +434,127 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = + new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public Union deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch(Union.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch(Union.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private Union deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + Union.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + Union.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(Union.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private Union deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "foo" -> 0; + case "bar" -> 1; + case "baz" -> 2; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new Union((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static Union deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + Union.class, "Expected the end of a JSON object while deserializing a union"); + } + return new Union(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java index e26284b3b..4aaac64ac 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java @@ -6,17 +6,25 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.Unsafe; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; @@ -34,6 +42,7 @@ /** A type which can either be a StringExample, a set of strings, or an integer. */ @Unsafe @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = UnionTypeExample.Deserializer.class) public final class UnionTypeExample { private final Base value; @@ -685,39 +694,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(StringExampleWrapper.class), - @JsonSubTypes.Type(ThisFieldIsAnIntegerWrapper.class), - @JsonSubTypes.Type(AlsoAnIntegerWrapper.class), - @JsonSubTypes.Type(IfWrapper.class), - @JsonSubTypes.Type(NewWrapper.class), - @JsonSubTypes.Type(InterfaceWrapper.class), - @JsonSubTypes.Type(CompletedWrapper.class), - @JsonSubTypes.Type(Unknown_Wrapper.class), - @JsonSubTypes.Type(OptionalWrapper.class), - @JsonSubTypes.Type(ListWrapper.class), - @JsonSubTypes.Type(SetWrapper.class), - @JsonSubTypes.Type(MapWrapper.class), - @JsonSubTypes.Type(OptionalAliasWrapper.class), - @JsonSubTypes.Type(ListAliasWrapper.class), - @JsonSubTypes.Type(SetAliasWrapper.class), - @JsonSubTypes.Type(MapAliasWrapper.class), - @JsonSubTypes.Type(BooleanFieldWrapper.class), - @JsonSubTypes.Type(SafeIntWrapper.class), - @JsonSubTypes.Type(UnsafeDoubleWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("stringExample") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class StringExampleWrapper implements Base { private final StringExample value; @@ -763,6 +745,7 @@ public String toString() { } @JsonTypeName("thisFieldIsAnInteger") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ThisFieldIsAnIntegerWrapper implements Base { private final int value; @@ -809,6 +792,7 @@ public String toString() { } @JsonTypeName("alsoAnInteger") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class AlsoAnIntegerWrapper implements Base { private final int value; @@ -854,6 +838,7 @@ public String toString() { } @JsonTypeName("if") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class IfWrapper implements Base { private final int value; @@ -899,6 +884,7 @@ public String toString() { } @JsonTypeName("new") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class NewWrapper implements Base { private final int value; @@ -944,6 +930,7 @@ public String toString() { } @JsonTypeName("interface") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class InterfaceWrapper implements Base { private final int value; @@ -989,6 +976,7 @@ public String toString() { } @JsonTypeName("completed") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class CompletedWrapper implements Base { private final int value; @@ -1034,6 +1022,7 @@ public String toString() { } @JsonTypeName("unknown") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class Unknown_Wrapper implements Base { private final int value; @@ -1079,6 +1068,7 @@ public String toString() { } @JsonTypeName("optional") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class OptionalWrapper implements Base { private final Optional value; @@ -1125,6 +1115,7 @@ public String toString() { } @JsonTypeName("list") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ListWrapper implements Base { private final List value; @@ -1170,6 +1161,7 @@ public String toString() { } @JsonTypeName("set") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SetWrapper implements Base { private final Set value; @@ -1217,6 +1209,7 @@ public String toString() { } @JsonTypeName("map") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class MapWrapper implements Base { private final Map value; @@ -1262,6 +1255,7 @@ public String toString() { } @JsonTypeName("optionalAlias") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class OptionalAliasWrapper implements Base { private final OptionalAlias value; @@ -1308,6 +1302,7 @@ public String toString() { } @JsonTypeName("listAlias") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class ListAliasWrapper implements Base { private final ListAlias value; @@ -1353,6 +1348,7 @@ public String toString() { } @JsonTypeName("setAlias") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SetAliasWrapper implements Base { private final SetAlias value; @@ -1398,6 +1394,7 @@ public String toString() { } @JsonTypeName("mapAlias") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class MapAliasWrapper implements Base { private final MapAliasExample value; @@ -1444,6 +1441,7 @@ public String toString() { } @JsonTypeName("booleanField") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BooleanFieldWrapper implements Base { private final boolean value; @@ -1489,6 +1487,7 @@ public String toString() { } @JsonTypeName("safeInt") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class SafeIntWrapper implements Base { private final int value; @@ -1534,6 +1533,7 @@ public String toString() { } @JsonTypeName("unsafeDouble") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class UnsafeDoubleWrapper implements Base { private final double value; @@ -1637,4 +1637,165 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] { + StringExampleWrapper.class, + ThisFieldIsAnIntegerWrapper.class, + AlsoAnIntegerWrapper.class, + IfWrapper.class, + NewWrapper.class, + InterfaceWrapper.class, + CompletedWrapper.class, + Unknown_Wrapper.class, + OptionalWrapper.class, + ListWrapper.class, + SetWrapper.class, + MapWrapper.class, + OptionalAliasWrapper.class, + ListAliasWrapper.class, + SetAliasWrapper.class, + MapAliasWrapper.class, + BooleanFieldWrapper.class, + SafeIntWrapper.class, + UnsafeDoubleWrapper.class + }; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public UnionTypeExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + UnionTypeExample.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionTypeExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private UnionTypeExample deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionTypeExample.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(UnionTypeExample.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "stringExample" -> 0; + case "thisFieldIsAnInteger" -> 1; + case "alsoAnInteger" -> 2; + case "if" -> 3; + case "new" -> 4; + case "interface" -> 5; + case "completed" -> 6; + case "unknown" -> 7; + case "optional" -> 8; + case "list" -> 9; + case "set" -> 10; + case "map" -> 11; + case "optionalAlias" -> 12; + case "listAlias" -> 13; + case "setAlias" -> 14; + case "mapAlias" -> 15; + case "booleanField" -> 16; + case "safeInt" -> 17; + case "unsafeDouble" -> 18; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new UnionTypeExample((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static UnionTypeExample deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + } + return new UnionTypeExample(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java index e6c83d3db..764a2c5e4 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java @@ -6,14 +6,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -23,6 +32,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = UnionWithUnknownString.Deserializer.class) public final class UnionWithUnknownString { private final Base value; @@ -145,19 +155,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes(@JsonSubTypes.Type(Unknown_Wrapper.class)) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("unknown") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class Unknown_Wrapper implements Base { private final String value; @@ -261,4 +264,129 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = new Class[] {Unknown_Wrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public UnionWithUnknownString deserialize(JsonParser parser, DeserializationContext context) + throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + UnionWithUnknownString.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private UnionWithUnknownString deserializeBuffered(JsonParser parser, DeserializationContext context) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionWithUnknownString.class, + "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(UnionWithUnknownString.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private UnionWithUnknownString deserializeSelected( + JsonParser parser, DeserializationContext context, String type) throws IOException { + int variantIndex = + switch (type) { + case "unknown" -> 0; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new UnionWithUnknownString((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static UnionWithUnknownString deserializeUnknown( + JsonParser parser, DeserializationContext context, String type) throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + UnionWithUnknownString.class, "Expected the end of a JSON object while deserializing a union"); + } + return new UnionWithUnknownString(new UnknownWrapper(type, values)); + } + } } diff --git a/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java index 83e645751..89fdd76b5 100644 --- a/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java @@ -6,15 +6,24 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,6 +34,7 @@ import javax.annotation.processing.Generated; @Generated("com.palantir.conjure.java.types.UnionGenerator") +@JsonDeserialize(using = SimpleUnion.Deserializer.class) public final class SimpleUnion { private final Base value; @@ -206,23 +216,12 @@ public interface Completed_StageVisitorBuilder { Visitor build(); } - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true, - defaultImpl = UnknownWrapper.class) - @JsonSubTypes({ - @JsonSubTypes.Type(FooWrapper.class), - @JsonSubTypes.Type(BarWrapper.class), - @JsonSubTypes.Type(BazWrapper.class) - }) - @JsonIgnoreProperties(ignoreUnknown = true) private interface Base { T accept(Visitor visitor); } @JsonTypeName("foo") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class FooWrapper implements Base { private final String value; @@ -268,6 +267,7 @@ public String toString() { } @JsonTypeName("bar") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BarWrapper implements Base { private final int value; @@ -313,6 +313,7 @@ public String toString() { } @JsonTypeName("baz") + @JsonIgnoreProperties(ignoreUnknown = true) private static final class BazWrapper implements Base { private final SafeLong value; @@ -416,4 +417,129 @@ public String toString() { return "UnknownWrapper{type: " + type + ", value: " + value + '}'; } } + + static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + private static final Class[] VARIANT_TYPES = + new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; + + private volatile JsonDeserializer[] deserializers; + + @Override + public void resolve(DeserializationContext context) throws JsonMappingException { + deserializers = new JsonDeserializer[VARIANT_TYPES.length]; + } + + @Override + public boolean isCachable() { + return true; + } + + @Override + public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected a JSON object for union deserialization"); + } + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context); + } + + private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, context)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch( + SimpleUnion.class, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = + JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, DeserializationContext context) { + return "type".equals(fieldName) + || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + && "type".equalsIgnoreCase(fieldName)); + } + + private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException { + int variantIndex = + switch (type) { + case "foo" -> 0; + case "bar" -> 1; + case "baz" -> 2; + default -> -1; + }; + if (variantIndex < 0) { + return deserializeUnknown(parser, context, type); + } + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return new SimpleUnion((Base) deserializer.deserialize(parser, context)); + } + + private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = deserializers[variantIndex]; + if (deserializer == null) { + deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); + JsonDeserializer[] updated = deserializers.clone(); + updated[variantIndex] = deserializer; + deserializers = updated; + } + return deserializer; + } + + private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) + throws IOException { + Map values = new HashMap<>(); + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + values.put(fieldName, context.readValue(parser, Object.class)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + } + return new SimpleUnion(new UnknownWrapper(type, values)); + } + } } From a879e648019c06d5d3be36f025682677881fe540 Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Fri, 11 Sep 2026 16:20:06 +0000 Subject: [PATCH 4/9] Address review feedback --- .../product/EmptyUnionTypeExample.java | 46 +++++++------- .../product/ExternalLongUnionExample.java | 47 +++++++------- .../palantir/product/LargeUnionExample.java | 46 +++++++------- .../com/palantir/product/SingleUnion.java | 47 +++++++------- .../com/palantir/product/Union.java | 47 +++++++------- .../palantir/product/UnionTypeExample.java | 46 +++++++------- .../product/UnionWithUnknownString.java | 46 +++++++------- .../ExampleDefensiveCollectionListsUnion.java | 47 +++++++------- .../ExampleDefensiveCollectionMapsUnion.java | 47 +++++++------- .../ExampleDefensiveCollectionSetsUnion.java | 47 +++++++------- .../ExampleDefensiveCollectionListsUnion.java | 47 +++++++------- .../ExampleDefensiveCollectionMapsUnion.java | 47 +++++++------- .../ExampleDefensiveCollectionSetsUnion.java | 47 +++++++------- .../com/palantir/product/EmptyUnion.java | 47 +++++++------- .../com/palantir/product/SimpleUnion.java | 47 +++++++------- .../com/palantir/product/UnionExample.java | 47 +++++++------- .../com/palantir/product/EmptyUnion.java | 47 +++++++------- .../com/palantir/product/UnionExample.java | 47 +++++++------- .../com/palantir/product/EmptyUnion.java | 47 +++++++------- .../com/palantir/product/SimpleUnion.java | 47 +++++++------- .../com/palantir/product/UnionExample.java | 47 +++++++------- .../product/external/UnionWithExternal.java | 46 +++++++------- .../com/palantir/product/SimpleUnion.java | 47 +++++++------- .../com/palantir/product/CamelCaseUnion.java | 46 +++++++------- .../com/palantir/product/EmptyUnion.java | 47 +++++++------- .../palantir/product/NestedEmptyUnion.java | 46 +++++++------- .../com/palantir/product/SimpleUnion.java | 47 +++++++------- .../palantir/product/UnionReservedNames.java | 46 +++++++------- .../product/EmptyUnionTypeExample.java | 46 +++++++------- .../product/ExternalLongUnionExample.java | 47 +++++++------- .../palantir/product/LargeUnionExample.java | 46 +++++++------- .../com/palantir/product/SingleUnion.java | 47 +++++++------- .../template/com/palantir/product/Union.java | 47 +++++++------- .../palantir/product/UnionTypeExample.java | 46 +++++++------- .../product/UnionWithUnknownString.java | 46 +++++++------- .../com/palantir/product/SimpleUnion.java | 47 +++++++------- .../conjure/java/types/UnionGenerator.java | 61 ++++++++----------- .../conjure/java/types/WireFormatTests.java | 20 ++++++ 38 files changed, 819 insertions(+), 942 deletions(-) diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java index 4b04044ac..e0fc1a51b 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java @@ -13,7 +13,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -23,6 +22,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -197,15 +197,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -218,8 +214,11 @@ public EmptyUnionTypeExample deserialize(JsonParser parser, DeserializationConte return context.reportInputMismatch( EmptyUnionTypeExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); @@ -228,10 +227,11 @@ public EmptyUnionTypeExample deserialize(JsonParser parser, DeserializationConte parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private EmptyUnionTypeExample deserializeBuffered(JsonParser parser, DeserializationContext context) + private EmptyUnionTypeExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -239,7 +239,7 @@ private EmptyUnionTypeExample deserializeBuffered(JsonParser parser, Deserializa while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); @@ -266,10 +266,8 @@ private EmptyUnionTypeExample deserializeBuffered(JsonParser parser, Deserializa return context.reportInputMismatch(EmptyUnionTypeExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private EmptyUnionTypeExample deserializeSelected( @@ -281,23 +279,21 @@ private EmptyUnionTypeExample deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new EmptyUnionTypeExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static EmptyUnionTypeExample deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java index c0759009e..1a98af860 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -26,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -277,16 +277,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer - implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {SafeLongWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -300,8 +295,11 @@ public ExternalLongUnionExample deserialize(JsonParser parser, DeserializationCo return context.reportInputMismatch( ExternalLongUnionExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); @@ -310,10 +308,11 @@ public ExternalLongUnionExample deserialize(JsonParser parser, DeserializationCo parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private ExternalLongUnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) + private ExternalLongUnionExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -321,7 +320,7 @@ private ExternalLongUnionExample deserializeBuffered(JsonParser parser, Deserial while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); @@ -349,10 +348,8 @@ private ExternalLongUnionExample deserializeBuffered(JsonParser parser, Deserial ExternalLongUnionExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private ExternalLongUnionExample deserializeSelected( @@ -365,23 +362,21 @@ private ExternalLongUnionExample deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new ExternalLongUnionExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static ExternalLongUnionExample deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java index 926bdd446..4b2cf85d6 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -25,6 +24,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.processing.Generated; @@ -5697,7 +5697,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { U0Wrapper.class, U1Wrapper.class, @@ -5802,12 +5802,8 @@ static final class Deserializer extends JsonDeserializer impl U100Wrapper.class }; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -5820,8 +5816,11 @@ public LargeUnionExample deserialize(JsonParser parser, DeserializationContext c return context.reportInputMismatch( LargeUnionExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( LargeUnionExample.class, "Union discriminator 'type' must be a string"); @@ -5830,10 +5829,11 @@ public LargeUnionExample deserialize(JsonParser parser, DeserializationContext c parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private LargeUnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) + private LargeUnionExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -5841,7 +5841,7 @@ private LargeUnionExample deserializeBuffered(JsonParser parser, Deserialization while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( LargeUnionExample.class, "Union discriminator 'type' must be a string"); @@ -5867,10 +5867,8 @@ private LargeUnionExample deserializeBuffered(JsonParser parser, Deserialization return context.reportInputMismatch(LargeUnionExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private LargeUnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -5983,23 +5981,21 @@ private LargeUnionExample deserializeSelected(JsonParser parser, Deserialization if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new LargeUnionExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static LargeUnionExample deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java index 3cb47a64c..c09b59c23 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -26,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -274,15 +274,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -295,8 +291,11 @@ public SingleUnion deserialize(JsonParser parser, DeserializationContext context return context.reportInputMismatch( SingleUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SingleUnion.class, "Union discriminator 'type' must be a string"); @@ -305,17 +304,19 @@ public SingleUnion deserialize(JsonParser parser, DeserializationContext context parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private SingleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private SingleUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SingleUnion.class, "Union discriminator 'type' must be a string"); @@ -341,10 +342,8 @@ private SingleUnion deserializeBuffered(JsonParser parser, DeserializationContex return context.reportInputMismatch(SingleUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private SingleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -357,23 +356,21 @@ private SingleUnion deserializeSelected(JsonParser parser, DeserializationContex if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new SingleUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static SingleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java index 2cf0aadfb..f06b6c2b9 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -26,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; @@ -445,16 +445,12 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -466,8 +462,11 @@ public Union deserialize(JsonParser parser, DeserializationContext context) thro if (!parser.isExpectedStartObjectToken()) { return context.reportInputMismatch(Union.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch(Union.class, "Union discriminator 'type' must be a string"); } @@ -475,17 +474,19 @@ public Union deserialize(JsonParser parser, DeserializationContext context) thro parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private Union deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private Union deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( Union.class, "Union discriminator 'type' must be a string"); @@ -511,10 +512,8 @@ private Union deserializeBuffered(JsonParser parser, DeserializationContext cont return context.reportInputMismatch(Union.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private Union deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -529,23 +528,21 @@ private Union deserializeSelected(JsonParser parser, DeserializationContext cont if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new Union((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static Union deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java index ef70e4e86..d8801954f 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -32,6 +31,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.DoubleFunction; import java.util.function.Function; @@ -1648,7 +1648,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringExampleWrapper.class, ThisFieldIsAnIntegerWrapper.class, @@ -1671,12 +1671,8 @@ static final class Deserializer extends JsonDeserializer imple UnsafeDoubleWrapper.class }; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -1689,8 +1685,11 @@ public UnionTypeExample deserialize(JsonParser parser, DeserializationContext co return context.reportInputMismatch( UnionTypeExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionTypeExample.class, "Union discriminator 'type' must be a string"); @@ -1699,10 +1698,11 @@ public UnionTypeExample deserialize(JsonParser parser, DeserializationContext co parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private UnionTypeExample deserializeBuffered(JsonParser parser, DeserializationContext context) + private UnionTypeExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -1710,7 +1710,7 @@ private UnionTypeExample deserializeBuffered(JsonParser parser, DeserializationC while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionTypeExample.class, "Union discriminator 'type' must be a string"); @@ -1736,10 +1736,8 @@ private UnionTypeExample deserializeBuffered(JsonParser parser, DeserializationC return context.reportInputMismatch(UnionTypeExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -1770,23 +1768,21 @@ private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationC if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new UnionTypeExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static UnionTypeExample deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java index e7a0e5f3b..d82cc68f0 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -26,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -275,15 +275,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Unknown_Wrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -297,8 +293,11 @@ public UnionWithUnknownString deserialize(JsonParser parser, DeserializationCont return context.reportInputMismatch( UnionWithUnknownString.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); @@ -307,10 +306,11 @@ public UnionWithUnknownString deserialize(JsonParser parser, DeserializationCont parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private UnionWithUnknownString deserializeBuffered(JsonParser parser, DeserializationContext context) + private UnionWithUnknownString deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -318,7 +318,7 @@ private UnionWithUnknownString deserializeBuffered(JsonParser parser, Deserializ while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); @@ -345,10 +345,8 @@ private UnionWithUnknownString deserializeBuffered(JsonParser parser, Deserializ return context.reportInputMismatch(UnionWithUnknownString.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private UnionWithUnknownString deserializeSelected( @@ -361,23 +359,21 @@ private UnionWithUnknownString deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new UnionWithUnknownString((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static UnionWithUnknownString deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java index ee5d4bbe2..3b7c0e65c 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; import com.palantir.logsafe.Preconditions; @@ -30,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -428,17 +428,12 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer - implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {ListWrapper.class, PrimitiveListWrapper.class, ListOptionalWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -452,8 +447,11 @@ public ExampleDefensiveCollectionListsUnion deserialize(JsonParser parser, Deser return context.reportInputMismatch( ExampleDefensiveCollectionListsUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' must be a string"); @@ -462,18 +460,19 @@ public ExampleDefensiveCollectionListsUnion deserialize(JsonParser parser, Deser parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } private ExampleDefensiveCollectionListsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context) throws IOException { + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionListsUnion.class, @@ -502,10 +501,8 @@ private ExampleDefensiveCollectionListsUnion deserializeBuffered( ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private ExampleDefensiveCollectionListsUnion deserializeSelected( @@ -520,23 +517,21 @@ private ExampleDefensiveCollectionListsUnion deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new ExampleDefensiveCollectionListsUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static ExampleDefensiveCollectionListsUnion deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java index c0d6c8848..8fab2139f 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -29,6 +28,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -354,16 +354,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer - implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {MapWrapper.class, MapOptionalWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -377,8 +372,11 @@ public ExampleDefensiveCollectionMapsUnion deserialize(JsonParser parser, Deseri return context.reportInputMismatch( ExampleDefensiveCollectionMapsUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' must be a string"); @@ -387,18 +385,19 @@ public ExampleDefensiveCollectionMapsUnion deserialize(JsonParser parser, Deseri parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } private ExampleDefensiveCollectionMapsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context) throws IOException { + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionMapsUnion.class, @@ -427,10 +426,8 @@ private ExampleDefensiveCollectionMapsUnion deserializeBuffered( ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private ExampleDefensiveCollectionMapsUnion deserializeSelected( @@ -444,23 +441,21 @@ private ExampleDefensiveCollectionMapsUnion deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new ExampleDefensiveCollectionMapsUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static ExampleDefensiveCollectionMapsUnion deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java index ebf32d516..ae9063130 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; import com.palantir.logsafe.Preconditions; @@ -30,6 +29,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -274,16 +274,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer - implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {SetWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -297,8 +292,11 @@ public ExampleDefensiveCollectionSetsUnion deserialize(JsonParser parser, Deseri return context.reportInputMismatch( ExampleDefensiveCollectionSetsUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' must be a string"); @@ -307,18 +305,19 @@ public ExampleDefensiveCollectionSetsUnion deserialize(JsonParser parser, Deseri parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } private ExampleDefensiveCollectionSetsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context) throws IOException { + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionSetsUnion.class, @@ -347,10 +346,8 @@ private ExampleDefensiveCollectionSetsUnion deserializeBuffered( ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private ExampleDefensiveCollectionSetsUnion deserializeSelected( @@ -363,23 +360,21 @@ private ExampleDefensiveCollectionSetsUnion deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new ExampleDefensiveCollectionSetsUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static ExampleDefensiveCollectionSetsUnion deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java index 1d0c6475b..068d6ab6a 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; import com.palantir.logsafe.Preconditions; @@ -30,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -428,17 +428,12 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer - implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {ListWrapper.class, PrimitiveListWrapper.class, ListOptionalWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -452,8 +447,11 @@ public ExampleDefensiveCollectionListsUnion deserialize(JsonParser parser, Deser return context.reportInputMismatch( ExampleDefensiveCollectionListsUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' must be a string"); @@ -462,18 +460,19 @@ public ExampleDefensiveCollectionListsUnion deserialize(JsonParser parser, Deser parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } private ExampleDefensiveCollectionListsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context) throws IOException { + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionListsUnion.class, @@ -502,10 +501,8 @@ private ExampleDefensiveCollectionListsUnion deserializeBuffered( ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private ExampleDefensiveCollectionListsUnion deserializeSelected( @@ -520,23 +517,21 @@ private ExampleDefensiveCollectionListsUnion deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new ExampleDefensiveCollectionListsUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static ExampleDefensiveCollectionListsUnion deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java index be925f0ee..e9da69a42 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -29,6 +28,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -352,16 +352,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer - implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {MapWrapper.class, MapOptionalWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -375,8 +370,11 @@ public ExampleDefensiveCollectionMapsUnion deserialize(JsonParser parser, Deseri return context.reportInputMismatch( ExampleDefensiveCollectionMapsUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' must be a string"); @@ -385,18 +383,19 @@ public ExampleDefensiveCollectionMapsUnion deserialize(JsonParser parser, Deseri parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } private ExampleDefensiveCollectionMapsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context) throws IOException { + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionMapsUnion.class, @@ -425,10 +424,8 @@ private ExampleDefensiveCollectionMapsUnion deserializeBuffered( ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private ExampleDefensiveCollectionMapsUnion deserializeSelected( @@ -442,23 +439,21 @@ private ExampleDefensiveCollectionMapsUnion deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new ExampleDefensiveCollectionMapsUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static ExampleDefensiveCollectionMapsUnion deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java index e5473d0a9..76b8e2c0b 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; import com.palantir.logsafe.Preconditions; @@ -30,6 +29,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -274,16 +274,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer - implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {SetWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -297,8 +292,11 @@ public ExampleDefensiveCollectionSetsUnion deserialize(JsonParser parser, Deseri return context.reportInputMismatch( ExampleDefensiveCollectionSetsUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' must be a string"); @@ -307,18 +305,19 @@ public ExampleDefensiveCollectionSetsUnion deserialize(JsonParser parser, Deseri parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } private ExampleDefensiveCollectionSetsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context) throws IOException { + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExampleDefensiveCollectionSetsUnion.class, @@ -347,10 +346,8 @@ private ExampleDefensiveCollectionSetsUnion deserializeBuffered( ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private ExampleDefensiveCollectionSetsUnion deserializeSelected( @@ -363,23 +360,21 @@ private ExampleDefensiveCollectionSetsUnion deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new ExampleDefensiveCollectionSetsUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static ExampleDefensiveCollectionSetsUnion deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java index 6480036b0..82c2b50e4 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -27,6 +26,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -116,15 +116,11 @@ public void serialize(EmptyUnion value, JsonGenerator generator, SerializerProvi } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -137,8 +133,11 @@ public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) return context.reportInputMismatch( EmptyUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); } @@ -146,17 +145,19 @@ public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private EmptyUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( EmptyUnion.class, "Union discriminator 'type' must be a string"); @@ -182,10 +183,8 @@ private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -197,23 +196,21 @@ private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return (EmptyUnion) deserializer.deserialize(parser, context); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java index 72905bc1d..582f875a5 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; @@ -30,6 +29,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -297,15 +297,11 @@ public void serialize(SimpleUnion value, JsonGenerator generator, SerializerProv } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Foo.class, Bar.class, Baz.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -318,8 +314,11 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context return context.reportInputMismatch( SimpleUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -328,17 +327,19 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private SimpleUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -364,10 +365,8 @@ private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContex return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -382,23 +381,21 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return (SimpleUnion) deserializer.deserialize(parser, context); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java index b78cdfb19..e2f22ef7e 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -32,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -417,17 +417,13 @@ public void serialize(UnionExample value, JsonGenerator generator, SerializerPro } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringVariant.class, IntVariant.class, ObjectVariant.class, CollectionVariant.class, OptionalVariant.class }; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -440,8 +436,11 @@ public UnionExample deserialize(JsonParser parser, DeserializationContext contex return context.reportInputMismatch( UnionExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionExample.class, "Union discriminator 'type' must be a string"); @@ -450,17 +449,19 @@ public UnionExample deserialize(JsonParser parser, DeserializationContext contex parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private UnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private UnionExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionExample.class, "Union discriminator 'type' must be a string"); @@ -486,10 +487,8 @@ private UnionExample deserializeBuffered(JsonParser parser, DeserializationConte return context.reportInputMismatch(UnionExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -506,23 +505,21 @@ private UnionExample deserializeSelected(JsonParser parser, DeserializationConte if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return (UnionExample) deserializer.deserialize(parser, context); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java index 18ca505dc..1818aa7fb 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java @@ -13,7 +13,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -23,6 +22,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -187,15 +187,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -208,8 +204,11 @@ public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) return context.reportInputMismatch( EmptyUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); } @@ -217,17 +216,19 @@ public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private EmptyUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( EmptyUnion.class, "Union discriminator 'type' must be a string"); @@ -253,10 +254,8 @@ private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -268,23 +267,21 @@ private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new EmptyUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java index d8844c6f1..8ae9cc4e5 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -29,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -578,7 +578,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringVariantWrapper.class, IntVariantWrapper.class, @@ -587,12 +587,8 @@ static final class Deserializer extends JsonDeserializer implement OptionalVariantWrapper.class }; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -605,8 +601,11 @@ public UnionExample deserialize(JsonParser parser, DeserializationContext contex return context.reportInputMismatch( UnionExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionExample.class, "Union discriminator 'type' must be a string"); @@ -615,17 +614,19 @@ public UnionExample deserialize(JsonParser parser, DeserializationContext contex parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private UnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private UnionExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionExample.class, "Union discriminator 'type' must be a string"); @@ -651,10 +652,8 @@ private UnionExample deserializeBuffered(JsonParser parser, DeserializationConte return context.reportInputMismatch(UnionExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -671,23 +670,21 @@ private UnionExample deserializeSelected(JsonParser parser, DeserializationConte if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new UnionExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java index 5c860aa83..46cbfb138 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java @@ -13,7 +13,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -23,6 +22,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -187,15 +187,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -208,8 +204,11 @@ public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) return context.reportInputMismatch( EmptyUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); } @@ -217,17 +216,19 @@ public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private EmptyUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( EmptyUnion.class, "Union discriminator 'type' must be a string"); @@ -253,10 +254,8 @@ private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -268,23 +267,21 @@ private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new EmptyUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java index 25741e338..59396b06a 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; @@ -27,6 +26,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -418,16 +418,12 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -440,8 +436,11 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context return context.reportInputMismatch( SimpleUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -450,17 +449,19 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private SimpleUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -486,10 +487,8 @@ private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContex return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -504,23 +503,21 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new SimpleUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java index b508d12f2..3ffb0c54e 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -29,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -578,7 +578,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringVariantWrapper.class, IntVariantWrapper.class, @@ -587,12 +587,8 @@ static final class Deserializer extends JsonDeserializer implement OptionalVariantWrapper.class }; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -605,8 +601,11 @@ public UnionExample deserialize(JsonParser parser, DeserializationContext contex return context.reportInputMismatch( UnionExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionExample.class, "Union discriminator 'type' must be a string"); @@ -615,17 +614,19 @@ public UnionExample deserialize(JsonParser parser, DeserializationContext contex parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private UnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private UnionExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionExample.class, "Union discriminator 'type' must be a string"); @@ -651,10 +652,8 @@ private UnionExample deserializeBuffered(JsonParser parser, DeserializationConte return context.reportInputMismatch(UnionExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -671,23 +670,21 @@ private UnionExample deserializeSelected(JsonParser parser, DeserializationConte if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new UnionExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java b/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java index 67807dd9e..5461295a9 100644 --- a/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java +++ b/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -26,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -275,15 +275,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FieldWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -296,8 +292,11 @@ public UnionWithExternal deserialize(JsonParser parser, DeserializationContext c return context.reportInputMismatch( UnionWithExternal.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionWithExternal.class, "Union discriminator 'type' must be a string"); @@ -306,10 +305,11 @@ public UnionWithExternal deserialize(JsonParser parser, DeserializationContext c parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private UnionWithExternal deserializeBuffered(JsonParser parser, DeserializationContext context) + private UnionWithExternal deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -317,7 +317,7 @@ private UnionWithExternal deserializeBuffered(JsonParser parser, Deserialization while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionWithExternal.class, "Union discriminator 'type' must be a string"); @@ -343,10 +343,8 @@ private UnionWithExternal deserializeBuffered(JsonParser parser, Deserialization return context.reportInputMismatch(UnionWithExternal.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private UnionWithExternal deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -359,23 +357,21 @@ private UnionWithExternal deserializeSelected(JsonParser parser, Deserialization if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new UnionWithExternal((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static UnionWithExternal deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java index e85d3b96a..dc3405254 100644 --- a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; @@ -27,6 +26,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -418,16 +418,12 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -440,8 +436,11 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context return context.reportInputMismatch( SimpleUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -450,17 +449,19 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private SimpleUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -486,10 +487,8 @@ private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContex return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -504,23 +503,21 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new SimpleUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java index 1b2a1d942..fcd3a9c0f 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -29,6 +28,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -186,15 +186,11 @@ public void serialize(CamelCaseUnion value, JsonGenerator generator, SerializerP } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {CamelCasedField.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -207,8 +203,11 @@ public CamelCaseUnion deserialize(JsonParser parser, DeserializationContext cont return context.reportInputMismatch( CamelCaseUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( CamelCaseUnion.class, "Union discriminator 'type' must be a string"); @@ -217,10 +216,11 @@ public CamelCaseUnion deserialize(JsonParser parser, DeserializationContext cont parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private CamelCaseUnion deserializeBuffered(JsonParser parser, DeserializationContext context) + private CamelCaseUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -228,7 +228,7 @@ private CamelCaseUnion deserializeBuffered(JsonParser parser, DeserializationCon while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( CamelCaseUnion.class, "Union discriminator 'type' must be a string"); @@ -254,10 +254,8 @@ private CamelCaseUnion deserializeBuffered(JsonParser parser, DeserializationCon return context.reportInputMismatch(CamelCaseUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private CamelCaseUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -270,23 +268,21 @@ private CamelCaseUnion deserializeSelected(JsonParser parser, DeserializationCon if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return (CamelCaseUnion) deserializer.deserialize(parser, context); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static CamelCaseUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java index ae28faf6a..5aaba2005 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -27,6 +26,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -117,15 +117,11 @@ public void serialize(EmptyUnion value, JsonGenerator generator, SerializerProvi } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -138,8 +134,11 @@ public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) return context.reportInputMismatch( EmptyUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); } @@ -147,17 +146,19 @@ public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private EmptyUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( EmptyUnion.class, "Union discriminator 'type' must be a string"); @@ -183,10 +184,8 @@ private EmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -198,23 +197,21 @@ private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return (EmptyUnion) deserializer.deserialize(parser, context); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java index a27667a87..1bc08e24b 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -29,6 +28,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -186,15 +186,11 @@ public void serialize(NestedEmptyUnion value, JsonGenerator generator, Serialize } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Empty.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -207,8 +203,11 @@ public NestedEmptyUnion deserialize(JsonParser parser, DeserializationContext co return context.reportInputMismatch( NestedEmptyUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( NestedEmptyUnion.class, "Union discriminator 'type' must be a string"); @@ -217,10 +216,11 @@ public NestedEmptyUnion deserialize(JsonParser parser, DeserializationContext co parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private NestedEmptyUnion deserializeBuffered(JsonParser parser, DeserializationContext context) + private NestedEmptyUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -228,7 +228,7 @@ private NestedEmptyUnion deserializeBuffered(JsonParser parser, DeserializationC while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( NestedEmptyUnion.class, "Union discriminator 'type' must be a string"); @@ -254,10 +254,8 @@ private NestedEmptyUnion deserializeBuffered(JsonParser parser, DeserializationC return context.reportInputMismatch(NestedEmptyUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private NestedEmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -270,23 +268,21 @@ private NestedEmptyUnion deserializeSelected(JsonParser parser, DeserializationC if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return (NestedEmptyUnion) deserializer.deserialize(parser, context); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static NestedEmptyUnion deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java index 96946ab20..e0c037959 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; @@ -30,6 +29,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; @@ -298,15 +298,11 @@ public void serialize(SimpleUnion value, JsonGenerator generator, SerializerProv } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Foo.class, Bar.class, Baz.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -319,8 +315,11 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context return context.reportInputMismatch( SimpleUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -329,17 +328,19 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private SimpleUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -365,10 +366,8 @@ private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContex return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -383,23 +382,21 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return (SimpleUnion) deserializer.deserialize(parser, context); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java index 5ad67c9c8..f0fe99fc6 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -29,6 +28,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -987,7 +987,7 @@ public void serialize(UnionReservedNames value, JsonGenerator generator, Seriali } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { Known_.class, Unknown_.class, @@ -1006,12 +1006,8 @@ static final class Deserializer extends JsonDeserializer imp UnionReservedNames_.class }; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -1024,8 +1020,11 @@ public UnionReservedNames deserialize(JsonParser parser, DeserializationContext return context.reportInputMismatch( UnionReservedNames.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionReservedNames.class, "Union discriminator 'type' must be a string"); @@ -1034,10 +1033,11 @@ public UnionReservedNames deserialize(JsonParser parser, DeserializationContext parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private UnionReservedNames deserializeBuffered(JsonParser parser, DeserializationContext context) + private UnionReservedNames deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -1045,7 +1045,7 @@ private UnionReservedNames deserializeBuffered(JsonParser parser, Deserializatio while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionReservedNames.class, "Union discriminator 'type' must be a string"); @@ -1071,10 +1071,8 @@ private UnionReservedNames deserializeBuffered(JsonParser parser, Deserializatio return context.reportInputMismatch(UnionReservedNames.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private UnionReservedNames deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -1101,23 +1099,21 @@ private UnionReservedNames deserializeSelected(JsonParser parser, Deserializatio if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return (UnionReservedNames) deserializer.deserialize(parser, context); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static UnionReservedNames deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java index 9512fa137..91df200b1 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java @@ -13,7 +13,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -23,6 +22,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -187,15 +187,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -208,8 +204,11 @@ public EmptyUnionTypeExample deserialize(JsonParser parser, DeserializationConte return context.reportInputMismatch( EmptyUnionTypeExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); @@ -218,10 +217,11 @@ public EmptyUnionTypeExample deserialize(JsonParser parser, DeserializationConte parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private EmptyUnionTypeExample deserializeBuffered(JsonParser parser, DeserializationContext context) + private EmptyUnionTypeExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -229,7 +229,7 @@ private EmptyUnionTypeExample deserializeBuffered(JsonParser parser, Deserializa while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); @@ -256,10 +256,8 @@ private EmptyUnionTypeExample deserializeBuffered(JsonParser parser, Deserializa return context.reportInputMismatch(EmptyUnionTypeExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private EmptyUnionTypeExample deserializeSelected( @@ -271,23 +269,21 @@ private EmptyUnionTypeExample deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new EmptyUnionTypeExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static EmptyUnionTypeExample deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java index 6e2d94371..0855d074f 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -26,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -267,16 +267,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer - implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {SafeLongWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -290,8 +285,11 @@ public ExternalLongUnionExample deserialize(JsonParser parser, DeserializationCo return context.reportInputMismatch( ExternalLongUnionExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); @@ -300,10 +298,11 @@ public ExternalLongUnionExample deserialize(JsonParser parser, DeserializationCo parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private ExternalLongUnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) + private ExternalLongUnionExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -311,7 +310,7 @@ private ExternalLongUnionExample deserializeBuffered(JsonParser parser, Deserial while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); @@ -339,10 +338,8 @@ private ExternalLongUnionExample deserializeBuffered(JsonParser parser, Deserial ExternalLongUnionExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private ExternalLongUnionExample deserializeSelected( @@ -355,23 +352,21 @@ private ExternalLongUnionExample deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new ExternalLongUnionExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static ExternalLongUnionExample deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java index d44851b16..ddfdd454a 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -25,6 +24,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.processing.Generated; @@ -5697,7 +5697,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { U0Wrapper.class, U1Wrapper.class, @@ -5802,12 +5802,8 @@ static final class Deserializer extends JsonDeserializer impl U100Wrapper.class }; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -5820,8 +5816,11 @@ public LargeUnionExample deserialize(JsonParser parser, DeserializationContext c return context.reportInputMismatch( LargeUnionExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( LargeUnionExample.class, "Union discriminator 'type' must be a string"); @@ -5830,10 +5829,11 @@ public LargeUnionExample deserialize(JsonParser parser, DeserializationContext c parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private LargeUnionExample deserializeBuffered(JsonParser parser, DeserializationContext context) + private LargeUnionExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -5841,7 +5841,7 @@ private LargeUnionExample deserializeBuffered(JsonParser parser, Deserialization while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( LargeUnionExample.class, "Union discriminator 'type' must be a string"); @@ -5867,10 +5867,8 @@ private LargeUnionExample deserializeBuffered(JsonParser parser, Deserialization return context.reportInputMismatch(LargeUnionExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private LargeUnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -5983,23 +5981,21 @@ private LargeUnionExample deserializeSelected(JsonParser parser, Deserialization if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new LargeUnionExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static LargeUnionExample deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java index f464f59a8..8f822d989 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -26,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -264,15 +264,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -285,8 +281,11 @@ public SingleUnion deserialize(JsonParser parser, DeserializationContext context return context.reportInputMismatch( SingleUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SingleUnion.class, "Union discriminator 'type' must be a string"); @@ -295,17 +294,19 @@ public SingleUnion deserialize(JsonParser parser, DeserializationContext context parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private SingleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private SingleUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SingleUnion.class, "Union discriminator 'type' must be a string"); @@ -331,10 +332,8 @@ private SingleUnion deserializeBuffered(JsonParser parser, DeserializationContex return context.reportInputMismatch(SingleUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private SingleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -347,23 +346,21 @@ private SingleUnion deserializeSelected(JsonParser parser, DeserializationContex if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new SingleUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static SingleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java index 215a0b8f3..ca2c32982 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -26,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -435,16 +435,12 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -456,8 +452,11 @@ public Union deserialize(JsonParser parser, DeserializationContext context) thro if (!parser.isExpectedStartObjectToken()) { return context.reportInputMismatch(Union.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch(Union.class, "Union discriminator 'type' must be a string"); } @@ -465,17 +464,19 @@ public Union deserialize(JsonParser parser, DeserializationContext context) thro parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private Union deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private Union deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( Union.class, "Union discriminator 'type' must be a string"); @@ -501,10 +502,8 @@ private Union deserializeBuffered(JsonParser parser, DeserializationContext cont return context.reportInputMismatch(Union.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private Union deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -519,23 +518,21 @@ private Union deserializeSelected(JsonParser parser, DeserializationContext cont if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new Union((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static Union deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java index 4aaac64ac..59412e5a5 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -32,6 +31,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.DoubleFunction; import java.util.function.Function; import java.util.function.IntFunction; @@ -1638,7 +1638,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringExampleWrapper.class, ThisFieldIsAnIntegerWrapper.class, @@ -1661,12 +1661,8 @@ static final class Deserializer extends JsonDeserializer imple UnsafeDoubleWrapper.class }; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -1679,8 +1675,11 @@ public UnionTypeExample deserialize(JsonParser parser, DeserializationContext co return context.reportInputMismatch( UnionTypeExample.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionTypeExample.class, "Union discriminator 'type' must be a string"); @@ -1689,10 +1688,11 @@ public UnionTypeExample deserialize(JsonParser parser, DeserializationContext co parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private UnionTypeExample deserializeBuffered(JsonParser parser, DeserializationContext context) + private UnionTypeExample deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -1700,7 +1700,7 @@ private UnionTypeExample deserializeBuffered(JsonParser parser, DeserializationC while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionTypeExample.class, "Union discriminator 'type' must be a string"); @@ -1726,10 +1726,8 @@ private UnionTypeExample deserializeBuffered(JsonParser parser, DeserializationC return context.reportInputMismatch(UnionTypeExample.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -1760,23 +1758,21 @@ private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationC if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new UnionTypeExample((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static UnionTypeExample deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java index 764a2c5e4..de02a1798 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; @@ -26,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -265,15 +265,11 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Unknown_Wrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -287,8 +283,11 @@ public UnionWithUnknownString deserialize(JsonParser parser, DeserializationCont return context.reportInputMismatch( UnionWithUnknownString.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); @@ -297,10 +296,11 @@ public UnionWithUnknownString deserialize(JsonParser parser, DeserializationCont parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private UnionWithUnknownString deserializeBuffered(JsonParser parser, DeserializationContext context) + private UnionWithUnknownString deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); @@ -308,7 +308,7 @@ private UnionWithUnknownString deserializeBuffered(JsonParser parser, Deserializ while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); @@ -335,10 +335,8 @@ private UnionWithUnknownString deserializeBuffered(JsonParser parser, Deserializ return context.reportInputMismatch(UnionWithUnknownString.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private UnionWithUnknownString deserializeSelected( @@ -351,23 +349,21 @@ private UnionWithUnknownString deserializeSelected( if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new UnionWithUnknownString((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static UnionWithUnknownString deserializeUnknown( diff --git a/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java index 89fdd76b5..154fbb1a4 100644 --- a/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; @@ -27,6 +26,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -418,16 +418,12 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer implements ResolvableDeserializer { + static final class Deserializer extends JsonDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private volatile JsonDeserializer[] deserializers; - - @Override - public void resolve(DeserializationContext context) throws JsonMappingException { - deserializers = new JsonDeserializer[VARIANT_TYPES.length]; - } + private final AtomicReferenceArray> variantDeserializers = + new AtomicReferenceArray<>(VARIANT_TYPES.length); @Override public boolean isCachable() { @@ -440,8 +436,11 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context return context.reportInputMismatch( SimpleUnion.class, "Expected a JSON object for union deserialization"); } + boolean acceptCaseInsensitiveProperties = + context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), context)) { + if (firstToken == JsonToken.FIELD_NAME + && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { if (parser.nextToken() != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -450,17 +449,19 @@ public SimpleUnion deserialize(JsonParser parser, DeserializationContext context parser.nextToken(); return deserializeSelected(parser, context, type); } - return deserializeBuffered(parser, context); + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); } - private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContext context) throws IOException { + private SimpleUnion deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { buffer.writeStartObject(); JsonToken token = parser.currentToken(); while (token == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, context)) { + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { if (valueToken != JsonToken.VALUE_STRING) { return context.reportInputMismatch( SimpleUnion.class, "Union discriminator 'type' must be a string"); @@ -486,10 +487,8 @@ private SimpleUnion deserializeBuffered(JsonParser parser, DeserializationContex return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); } - private static boolean isTypeField(String fieldName, DeserializationContext context) { - return "type".equals(fieldName) - || (context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - && "type".equalsIgnoreCase(fieldName)); + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); } private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) @@ -504,23 +503,21 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex if (variantIndex < 0) { return deserializeUnknown(parser, context, type); } - JsonDeserializer deserializer = deserializers[variantIndex]; + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); if (deserializer == null) { deserializer = resolveDeserializer(context, variantIndex); } return new SimpleUnion((Base) deserializer.deserialize(parser, context)); } - private synchronized JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) throws JsonMappingException { - JsonDeserializer deserializer = deserializers[variantIndex]; - if (deserializer == null) { - deserializer = context.findRootValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex])); - JsonDeserializer[] updated = deserializers.clone(); - updated[variantIndex] = deserializer; - deserializers = updated; + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; } - return deserializer; + return variantDeserializers.get(variantIndex); } private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java index a6d0a37e5..c4398e6de 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java @@ -36,7 +36,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; @@ -83,6 +82,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.DoubleFunction; import java.util.function.Function; @@ -838,10 +838,8 @@ private static TypeSpec generateDeserializer( TypeSpec.Builder builder = TypeSpec.classBuilder(deserializerClass) .addModifiers(Modifier.STATIC, Modifier.FINAL) .superclass(ParameterizedTypeName.get(ClassName.get(JsonDeserializer.class), unionClass)) - .addSuperinterface(ResolvableDeserializer.class) .addField(generateVariantTypesField(unionClass, memberTypeDefs, options)) - .addField(generateDeserializersField()) - .addMethod(generateResolveMethod()) + .addField(generateVariantDeserializersField()) .addMethod(generateIsCachableMethod()) .addMethod(generateDeserializeMethod(unionClass)) .addMethod(generateDeserializeBufferedMethod(unionClass)) @@ -874,21 +872,12 @@ private static FieldSpec generateVariantTypesField( .build(); } - private static FieldSpec generateDeserializersField() { + private static FieldSpec generateVariantDeserializersField() { TypeName deserializerType = ParameterizedTypeName.get( ClassName.get(JsonDeserializer.class), WildcardTypeName.subtypeOf(Object.class)); - return FieldSpec.builder( - ArrayTypeName.of(deserializerType), "deserializers", Modifier.PRIVATE, Modifier.VOLATILE) - .build(); - } - - private static MethodSpec generateResolveMethod() { - return MethodSpec.methodBuilder("resolve") - .addAnnotation(Override.class) - .addModifiers(Modifier.PUBLIC) - .addParameter(DeserializationContext.class, "context") - .addException(JsonMappingException.class) - .addStatement("deserializers = new $T[VARIANT_TYPES.length]", JsonDeserializer.class) + TypeName cacheType = ParameterizedTypeName.get(ClassName.get(AtomicReferenceArray.class), deserializerType); + return FieldSpec.builder(cacheType, "variantDeserializers", Modifier.PRIVATE, Modifier.FINAL) + .initializer("new $T<>(VARIANT_TYPES.length)", AtomicReferenceArray.class) .build(); } @@ -932,9 +921,14 @@ private static MethodSpec generateDeserializeMethod(ClassName unionClass) { unionClass, "Expected a JSON object for union deserialization") .endControlFlow() + .addStatement( + "boolean acceptCaseInsensitiveProperties = context.isEnabled(" + + "$T.ACCEPT_CASE_INSENSITIVE_PROPERTIES)", + MapperFeature.class) .addStatement("$T firstToken = parser.nextToken()", JsonToken.class) .beginControlFlow( - "if (firstToken == $T.FIELD_NAME && isTypeField(parser.currentName(), context))", + "if (firstToken == $T.FIELD_NAME" + + " && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties))", JsonToken.class) .beginControlFlow("if (parser.nextToken() != $T.VALUE_STRING)", JsonToken.class) .addStatement( @@ -946,7 +940,7 @@ private static MethodSpec generateDeserializeMethod(ClassName unionClass) { .addStatement("parser.nextToken()") .addStatement("return deserializeSelected(parser, context, type)") .endControlFlow() - .addStatement("return deserializeBuffered(parser, context)") + .addStatement("return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties)") .build(); } @@ -956,6 +950,7 @@ private static MethodSpec generateDeserializeBufferedMethod(ClassName unionClass .returns(unionClass) .addParameter(JsonParser.class, "parser") .addParameter(DeserializationContext.class, "context") + .addParameter(TypeName.BOOLEAN, "acceptCaseInsensitiveProperties") .addException(IOException.class) .beginControlFlow("try ($T buffer = context.bufferForInputBuffering(parser))", TokenBuffer.class) .addStatement("buffer.writeStartObject()") @@ -963,7 +958,7 @@ private static MethodSpec generateDeserializeBufferedMethod(ClassName unionClass .beginControlFlow("while (token == $T.FIELD_NAME)", JsonToken.class) .addStatement("$T fieldName = parser.currentName()", String.class) .addStatement("$T valueToken = parser.nextToken()", JsonToken.class) - .beginControlFlow("if (isTypeField(fieldName, context))") + .beginControlFlow("if (isTypeField(fieldName, acceptCaseInsensitiveProperties))") .beginControlFlow("if (valueToken != $T.VALUE_STRING)", JsonToken.class) .addStatement( "return context.reportInputMismatch($T.class, $S)", @@ -1004,12 +999,11 @@ private static MethodSpec generateIsTypeFieldMethod() { .addModifiers(Modifier.PRIVATE, Modifier.STATIC) .returns(TypeName.BOOLEAN) .addParameter(String.class, "fieldName") - .addParameter(DeserializationContext.class, "context") + .addParameter(TypeName.BOOLEAN, "acceptCaseInsensitiveProperties") .addStatement( - "return $S.equals(fieldName) || (context.isEnabled($T.ACCEPT_CASE_INSENSITIVE_PROPERTIES)" - + " && $S.equalsIgnoreCase(fieldName))", + "return $S.equals(fieldName)" + + " || (acceptCaseInsensitiveProperties && $S.equalsIgnoreCase(fieldName))", "type", - MapperFeature.class, "type") .build(); } @@ -1031,7 +1025,7 @@ private static MethodSpec generateDeserializeSelectedMethod( builder.beginControlFlow("if (variantIndex < 0)"); builder.addStatement("return deserializeUnknown(parser, context, type)"); builder.endControlFlow(); - builder.addStatement("$T deserializer = deserializers[variantIndex]", JsonDeserializer.class) + builder.addStatement("$T deserializer = variantDeserializers.get(variantIndex)", JsonDeserializer.class) .beginControlFlow("if (deserializer == null)") .addStatement("deserializer = resolveDeserializer(context, variantIndex)") .endControlFlow(); @@ -1050,20 +1044,19 @@ private static MethodSpec generateResolveDeserializerMethod() { ParameterizedTypeName deserializerType = ParameterizedTypeName.get( ClassName.get(JsonDeserializer.class), WildcardTypeName.subtypeOf(Object.class)); return MethodSpec.methodBuilder("resolveDeserializer") - .addModifiers(Modifier.PRIVATE, Modifier.SYNCHRONIZED) + .addModifiers(Modifier.PRIVATE) .returns(deserializerType) .addParameter(DeserializationContext.class, "context") .addParameter(TypeName.INT, "variantIndex") .addException(JsonMappingException.class) - .addStatement("$T deserializer = deserializers[variantIndex]", deserializerType) - .beginControlFlow("if (deserializer == null)") - .addStatement("deserializer = context.findRootValueDeserializer(" - + "context.constructType(VARIANT_TYPES[variantIndex]))") - .addStatement("$T[] updated = deserializers.clone()", deserializerType) - .addStatement("updated[variantIndex] = deserializer") - .addStatement("deserializers = updated") - .endControlFlow() + .addStatement( + "$T deserializer = context.findContextualValueDeserializer(" + + "context.constructType(VARIANT_TYPES[variantIndex]), null)", + deserializerType) + .beginControlFlow("if (variantDeserializers.compareAndSet(variantIndex, null, deserializer))") .addStatement("return deserializer") + .endControlFlow() + .addStatement("return variantDeserializers.get(variantIndex)") .build(); } diff --git a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/WireFormatTests.java b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/WireFormatTests.java index 4797e0ab4..033bd5113 100644 --- a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/WireFormatTests.java +++ b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/WireFormatTests.java @@ -52,6 +52,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.InvalidNullException; import com.google.common.collect.ImmutableList; @@ -878,6 +879,25 @@ void testSealedUnionType_deserialize() throws JsonProcessingException { .isEqualTo(SimpleUnion.foo("test")); } + @Test + void testUnionType_deserialize_caseInsensitiveDiscriminator() throws JsonProcessingException { + ObjectMapper caseInsensitiveMapper = mapper.copy(); + caseInsensitiveMapper.setConfig(caseInsensitiveMapper + .getDeserializationConfig() + .with(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)); + + assertThat(caseInsensitiveMapper.readValue("{\"TYPE\":\"foo\",\"foo\":\"first\"}", SimpleUnion.class)) + .isEqualTo(SimpleUnion.foo("first")); + assertThat(caseInsensitiveMapper.readValue("{\"foo\":\"last\",\"TYPE\":\"foo\"}", SimpleUnion.class)) + .isEqualTo(SimpleUnion.foo("last")); + assertThat(caseInsensitiveMapper.readValue( + "{\"TYPE\":\"thisFieldIsAnInteger\",\"thisFieldIsAnInteger\":42}", UnionTypeExample.class)) + .isEqualTo(UnionTypeExample.thisFieldIsAnInteger(42)); + assertThat(caseInsensitiveMapper.readValue( + "{\"thisFieldIsAnInteger\":42,\"TYPE\":\"thisFieldIsAnInteger\"}", UnionTypeExample.class)) + .isEqualTo(UnionTypeExample.thisFieldIsAnInteger(42)); + } + @Test void testSealedUnionType_serialize_unknownVariant() throws JsonProcessingException { SimpleUnion simpleUnion = SimpleUnion.unknown("test", Map.of("foo", "bar")); From e5ff6e3205630b2ea886429652a11fa2b0c7314f Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Fri, 25 Sep 2026 13:53:36 +0000 Subject: [PATCH 5/9] Improve union serDe --- .../conjure/java/types/UnionGenerator.java | 151 ++++++++++++++---- readme.md | 2 + 2 files changed, 122 insertions(+), 31 deletions(-) diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java index c4398e6de..95ecb7007 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -43,6 +44,7 @@ import com.palantir.conjure.java.ConjureAnnotations; import com.palantir.conjure.java.Options; import com.palantir.conjure.java.lib.internal.ConjureCollections; +import com.palantir.conjure.java.lib.internal.ConjureMapDeserializer; import com.palantir.conjure.java.util.JavaNameSanitizer; import com.palantir.conjure.java.util.Javadoc; import com.palantir.conjure.java.util.Packages; @@ -63,6 +65,7 @@ import com.palantir.javapoet.FieldSpec; import com.palantir.javapoet.JavaFile; import com.palantir.javapoet.MethodSpec; +import com.palantir.javapoet.NameAllocator; import com.palantir.javapoet.ParameterSpec; import com.palantir.javapoet.ParameterizedTypeName; import com.palantir.javapoet.TypeName; @@ -133,6 +136,25 @@ public static JavaFile generateUnionType( entry -> ConjureAnnotations.withSafety( typeMapper.getClassName(entry.getType()), safetyEvaluator.getUsageTimeSafety(entry)))); + NameAllocator helperNames = new NameAllocator(); + helperNames.newName(unionClass.simpleName()); + helperNames.newName("Base"); + helperNames.newName("Visitor"); + helperNames.newName("VisitorBuilder"); + helperNames.newName(UNKNOWN_WRAPPER_CLASS_NAME); + helperNames.newName(SEALED_KNOWN_INTERFACE); + helperNames.newName(SEALED_UNKNOWN_VARIANT_NAME); + for (FieldDefinition member : typeDef.getUnion()) { + helperNames.newName( + options.sealedUnions() + ? sealedVariantClass(unionClass, member.getFieldName()) + .simpleName() + : wrapperClass(unionClass, sanitizeUnknown(member.getFieldName())) + .simpleName()); + } + ClassName deserializerClass = unionClass.nestedClass(helperNames.newName(DESERIALIZER_CLASS_NAME)); + ClassName serializerClass = unionClass.nestedClass(helperNames.newName(SERIALIZER_CLASS_NAME)); + if (options.sealedUnions()) { ClassName unknownVariant = unionClass.nestedClass(SEALED_UNKNOWN_VARIANT_NAME); List safety = @@ -142,8 +164,8 @@ public static JavaFile generateUnionType( typeDef.getTypeName().getName()) .addAnnotations(safety) .addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(UnionGenerator.class)) - .addAnnotation(generateJsonDeserialize(unionClass)) - .addAnnotation(generateJsonSerialize(unionClass)) + .addAnnotation(generateJsonDeserialize(deserializerClass)) + .addAnnotation(generateJsonSerialize(serializerClass)) .addAnnotation(ignoreUnknownAnnotation()) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT, Modifier.SEALED) .addPermittedSubclasses(typeDef.getUnion().stream() @@ -157,8 +179,8 @@ public static JavaFile generateUnionType( .addTypes(generateWrapperClasses( typeMapper, typesMap, unionClass, visitorClass, typeDef.getUnion(), options)) .addType(generateUnknownWrapper(unionClass, visitorClass, options)) - .addType(generateSerializer(unionClass)) - .addType(generateDeserializer(unionClass, typeDef.getUnion(), options)); + .addType(generateSerializer(unionClass, serializerClass)) + .addType(generateDeserializer(unionClass, deserializerClass, typeDef.getUnion(), options)); typeDef.getDocs().ifPresent(docs -> typeBuilder.addJavadoc("$L", Javadoc.render(docs))); @@ -193,7 +215,7 @@ public static JavaFile generateUnionType( typeDef.getTypeName().getName()) .addAnnotations(safety) .addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(UnionGenerator.class)) - .addAnnotation(generateJsonDeserialize(unionClass)) + .addAnnotation(generateJsonDeserialize(deserializerClass)) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addFields(fields) .addMethod(generateConstructor(baseClass)) @@ -212,7 +234,7 @@ public static JavaFile generateUnionType( .addTypes(generateWrapperClasses( typeMapper, typesMap, baseClass, visitorClass, typeDef.getUnion(), options)) .addType(generateUnknownWrapper(baseClass, visitorClass, options)) - .addType(generateDeserializer(unionClass, typeDef.getUnion(), options)) + .addType(generateDeserializer(unionClass, deserializerClass, typeDef.getUnion(), options)) .addMethod(MethodSpecs.createEquals(unionClass)) .addMethod(MethodSpecs.createEqualTo(unionClass, fields)) .addMethod(MethodSpecs.createHashCode(fields)) @@ -256,15 +278,15 @@ private static MethodSpec createLegacyToStringForSealedUnions(ClassName baseClas .build(); } - private static AnnotationSpec generateJsonDeserialize(ClassName unionClass) { + private static AnnotationSpec generateJsonDeserialize(ClassName deserializerClass) { return AnnotationSpec.builder(JsonDeserialize.class) - .addMember("using", "$T.class", unionClass.nestedClass(DESERIALIZER_CLASS_NAME)) + .addMember("using", "$T.class", deserializerClass) .build(); } - private static AnnotationSpec generateJsonSerialize(ClassName unionClass) { + private static AnnotationSpec generateJsonSerialize(ClassName serializerClass) { return AnnotationSpec.builder(JsonSerialize.class) - .addMember("using", "$T.class", unionClass.nestedClass(SERIALIZER_CLASS_NAME)) + .addMember("using", "$T.class", serializerClass) .build(); } @@ -833,8 +855,7 @@ private static Stream sortedStageNameTypePairs(Map memberTypeDefs, Options options) { - ClassName deserializerClass = unionClass.nestedClass(DESERIALIZER_CLASS_NAME); + ClassName unionClass, ClassName deserializerClass, List memberTypeDefs, Options options) { TypeSpec.Builder builder = TypeSpec.classBuilder(deserializerClass) .addModifiers(Modifier.STATIC, Modifier.FINAL) .superclass(ParameterizedTypeName.get(ClassName.get(JsonDeserializer.class), unionClass)) @@ -890,8 +911,8 @@ private static MethodSpec generateIsCachableMethod() { .build(); } - private static TypeSpec generateSerializer(ClassName unionClass) { - return TypeSpec.classBuilder(unionClass.nestedClass(SERIALIZER_CLASS_NAME)) + private static TypeSpec generateSerializer(ClassName unionClass, ClassName serializerClass) { + return TypeSpec.classBuilder(serializerClass) .addModifiers(Modifier.STATIC, Modifier.FINAL) .superclass(ParameterizedTypeName.get(ClassName.get(JsonSerializer.class), unionClass)) .addMethod(MethodSpec.methodBuilder("serialize") @@ -1070,13 +1091,23 @@ private static MethodSpec generateDeserializeUnknownMethod(ClassName unionClass, .addParameter(String.class, "type") .addException(IOException.class) .addStatement("$T values = new $T<>()", valueMapType, HashMap.class) + .addStatement("$T<$T> valueDeserializer = null", JsonDeserializer.class, Object.class) .beginControlFlow("if (parser.currentToken() == $T.START_OBJECT)", JsonToken.class) .addStatement("parser.nextToken()") .endControlFlow() .beginControlFlow("while (parser.currentToken() == $T.FIELD_NAME)", JsonToken.class) .addStatement("$T fieldName = parser.currentName()", String.class) .addStatement("parser.nextToken()") - .addStatement("values.put(fieldName, context.readValue(parser, $T.class))", Object.class) + .beginControlFlow("if (valueDeserializer == null)") + .addStatement( + "valueDeserializer = context.findRootValueDeserializer(context.constructType($T.class))", + Object.class) + .endControlFlow() + .addStatement( + "values.put(fieldName, parser.currentToken() == $T.VALUE_NULL ?" + + " valueDeserializer.getNullValue(context) : valueDeserializer.deserialize(parser," + + " context))", + JsonToken.class) .addStatement("parser.nextToken()") .endControlFlow() .beginControlFlow("if (parser.currentToken() != $T.END_OBJECT)", JsonToken.class) @@ -1143,6 +1174,10 @@ private static List generateWrapperClasses( ClassName visitorClass, List memberTypeDefs, Options options) { + NameAllocator methodNames = new NameAllocator(); + memberTypeDefs.forEach( + member -> methodNames.newName(JavaNameSanitizer.sanitize(sanitizeUnknown(member.getFieldName())))); + String jsonFactoryName = methodNames.newName("fromJson"); return memberTypeDefs.stream() .map(memberTypeDef -> { boolean isDeprecated = memberTypeDef.getDeprecated().isPresent(); @@ -1165,23 +1200,17 @@ private static List generateWrapperClasses( .addMember("value", "$S", memberTypeDef.getFieldName()) .build()) .addAnnotation(ignoreUnknownAnnotation()) + .addAnnotation(typeFirstAnnotation()) .addFields(fields) - .addMethod(MethodSpec.constructorBuilder() - .addModifiers(Modifier.PRIVATE) - .addAnnotation(ConjureAnnotations.propertiesJsonCreator()) - .addParameter(ParameterSpec.builder(memberType, VALUE_FIELD_NAME) - .addAnnotation(wrapperConstructorParameterAnnotation( - memberTypeDef, typeMapper, typesMap, options)) - .addAnnotations(deserializationAnnotationForSets(memberTypeDef)) - .addAnnotation(Nonnull.class) - .build()) - .addStatement( - "$L", - Expressions.requireNonNull( - VALUE_FIELD_NAME, - String.format("%s cannot be null", memberName.get()))) - .addStatement(createConstructor(memberTypeDef.getType(), options)) - .build()) + .addMethods(generateWrapperConstructors( + wrapperClass, + memberType, + memberTypeDef, + memberName, + typeMapper, + typesMap, + options, + jsonFactoryName)) .addMethod(MethodSpec.methodBuilder(options.sealedUnions() ? "type" : "getType") .addModifiers(Modifier.PRIVATE) .addAnnotation(getTypeJsonPropertyAnnotation(options)) @@ -1242,6 +1271,65 @@ baseClass, peerWrapperClass(baseClass, memberName)) .collect(Collectors.toList()); } + private static List generateWrapperConstructors( + ClassName wrapperClass, + TypeName memberType, + FieldDefinition memberTypeDef, + FieldName memberName, + TypeMapper typeMapper, + Map typesMap, + Options options, + String jsonFactoryName) { + ParameterSpec.Builder jsonParameter = ParameterSpec.builder(memberType, VALUE_FIELD_NAME) + .addAnnotation(wrapperConstructorParameterAnnotation(memberTypeDef, typeMapper, typesMap, options)) + .addAnnotations(deserializationAnnotationForSets(memberTypeDef)) + .addAnnotation(Nonnull.class); + CodeBlock checkNotNull = + Expressions.requireNonNull(VALUE_FIELD_NAME, String.format("%s cannot be null", memberName.get())); + if (options.defensiveCollections() && memberTypeDef.getType().accept(TypeVisitor.IS_MAP)) { + jsonParameter.addAnnotation(AnnotationSpec.builder(JsonDeserialize.class) + .addMember("using", "$T.class", ConjureMapDeserializer.class) + .build()); + return List.of( + MethodSpec.constructorBuilder() + .addModifiers(Modifier.PRIVATE) + .addParameter(memberType, VALUE_FIELD_NAME) + .addStatement("this($L, false)", VALUE_FIELD_NAME) + .build(), + MethodSpec.methodBuilder(jsonFactoryName) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addAnnotation(ConjureAnnotations.propertiesJsonCreator()) + .addParameter(jsonParameter.build()) + .returns(wrapperClass) + .addStatement("return new $T($L, true)", wrapperClass, VALUE_FIELD_NAME) + .build(), + MethodSpec.constructorBuilder() + .addModifiers(Modifier.PRIVATE) + .addParameter(memberType, VALUE_FIELD_NAME) + .addParameter(TypeName.BOOLEAN, "owned") + .addStatement("$L", checkNotNull) + .addStatement( + "this.$1L = $2T.unmodifiableMap(owned ? $1L : new $3T<>($1L))", + VALUE_FIELD_NAME, + Collections.class, + LinkedHashMap.class) + .build()); + } + return List.of(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PRIVATE) + .addAnnotation(ConjureAnnotations.propertiesJsonCreator()) + .addParameter(jsonParameter.build()) + .addStatement("$L", checkNotNull) + .addStatement(createConstructor(memberTypeDef.getType(), options)) + .build()); + } + + private static AnnotationSpec typeFirstAnnotation() { + return AnnotationSpec.builder(JsonPropertyOrder.class) + .addMember("value", "$S", "type") + .build(); + } + private static AnnotationSpec getTypeJsonPropertyAnnotation(Options options) { AnnotationSpec.Builder builder = AnnotationSpec.builder(JsonProperty.class); if (!options.sealedUnions()) { @@ -1305,6 +1393,7 @@ private static TypeSpec generateUnknownWrapper(ClassName baseClass, ClassName vi FieldSpec.builder(genericMapType, VALUE_FIELD_NAME, Modifier.PRIVATE, Modifier.FINAL) .build()); TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(wrapperClass) + .addAnnotation(typeFirstAnnotation()) .addModifiers( options.sealedUnions() ? Modifier.PUBLIC : Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) .addFields(fields) diff --git a/readme.md b/readme.md index fd1bbd667..c75a7a12e 100644 --- a/readme.md +++ b/readme.md @@ -117,6 +117,8 @@ Conjure-java objects are always immutable and thread-safe. Fields are never nul Union types can be one of a few variants. To interact with a union value, users should use the `.accept` method and define a Visitor that handles each of the possible variants, including the possibility of an unknown variant. + Jackson writes the `type` discriminator first so generated readers can avoid buffering. Readers also accept the discriminator in any position. With `--defensiveCollections`, public union factories copy input maps. Jackson-created maps are wrapped without an additional copy; maps from custom deserializers or custom map constructors are copied to preserve ownership and immutability. + ```java Foo output = unionTypeExample.accept(new Visitor() { From 40637ad61397cafe1c6075b10174d4598963c2b5 Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Fri, 25 Sep 2026 13:53:55 +0000 Subject: [PATCH 6/9] generated test code --- .../product/EmptyUnionTypeExample.java | 12 +- .../product/ExternalLongUnionExample.java | 13 +- .../palantir/product/LargeUnionExample.java | 113 +++++++++++++++++- .../com/palantir/product/SingleUnion.java | 13 +- .../com/palantir/product/Union.java | 15 ++- .../palantir/product/UnionTypeExample.java | 31 ++++- .../product/UnionWithUnknownString.java | 13 +- .../ExampleDefensiveCollectionListsUnion.java | 15 ++- .../ExampleDefensiveCollectionMapsUnion.java | 51 ++++++-- .../ExampleDefensiveCollectionSetsUnion.java | 13 +- .../ExampleDefensiveCollectionListsUnion.java | 15 ++- .../ExampleDefensiveCollectionMapsUnion.java | 51 ++++++-- .../ExampleDefensiveCollectionSetsUnion.java | 13 +- .../com/palantir/product/EmptyUnion.java | 12 +- .../com/palantir/product/SimpleUnion.java | 15 ++- .../com/palantir/product/UnionExample.java | 17 ++- .../com/palantir/product/EmptyUnion.java | 12 +- .../com/palantir/product/UnionExample.java | 17 ++- .../com/palantir/product/EmptyUnion.java | 12 +- .../com/palantir/product/SimpleUnion.java | 15 ++- .../com/palantir/product/UnionExample.java | 17 ++- .../product/external/UnionWithExternal.java | 13 +- .../com/palantir/product/SimpleUnion.java | 15 ++- .../com/palantir/product/CamelCaseUnion.java | 13 +- .../com/palantir/product/EmptyUnion.java | 12 +- .../palantir/product/NestedEmptyUnion.java | 13 +- .../com/palantir/product/SimpleUnion.java | 15 ++- .../palantir/product/UnionReservedNames.java | 27 ++++- .../product/EmptyUnionTypeExample.java | 12 +- .../product/ExternalLongUnionExample.java | 13 +- .../palantir/product/LargeUnionExample.java | 113 +++++++++++++++++- .../com/palantir/product/SingleUnion.java | 13 +- .../template/com/palantir/product/Union.java | 15 ++- .../palantir/product/UnionTypeExample.java | 31 ++++- .../product/UnionWithUnknownString.java | 13 +- .../com/palantir/product/SimpleUnion.java | 15 ++- 36 files changed, 767 insertions(+), 51 deletions(-) diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java index e0fc1a51b..59b0cf7a0 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -137,6 +138,7 @@ private interface Base { T accept(Visitor visitor); } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -299,13 +301,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static EmptyUnionTypeExample deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java index 1a98af860..d4c4fcd98 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -173,6 +174,7 @@ private interface Base { @JsonTypeName("safeLong") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SafeLongWrapper implements Base { private final long value; @@ -217,6 +219,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -382,13 +385,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static ExternalLongUnionExample deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java index 4b2cf85d6..0f5f29e13 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -993,6 +994,7 @@ private interface Base { @JsonTypeName("u0") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U0Wrapper implements Base { private final String value; @@ -1039,6 +1041,7 @@ public String toString() { @JsonTypeName("u1") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U1Wrapper implements Base { private final String value; @@ -1085,6 +1088,7 @@ public String toString() { @JsonTypeName("u2") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U2Wrapper implements Base { private final String value; @@ -1131,6 +1135,7 @@ public String toString() { @JsonTypeName("u3") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U3Wrapper implements Base { private final String value; @@ -1177,6 +1182,7 @@ public String toString() { @JsonTypeName("u4") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U4Wrapper implements Base { private final String value; @@ -1223,6 +1229,7 @@ public String toString() { @JsonTypeName("u5") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U5Wrapper implements Base { private final String value; @@ -1269,6 +1276,7 @@ public String toString() { @JsonTypeName("u6") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U6Wrapper implements Base { private final String value; @@ -1315,6 +1323,7 @@ public String toString() { @JsonTypeName("u7") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U7Wrapper implements Base { private final String value; @@ -1361,6 +1370,7 @@ public String toString() { @JsonTypeName("u8") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U8Wrapper implements Base { private final String value; @@ -1407,6 +1417,7 @@ public String toString() { @JsonTypeName("u9") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U9Wrapper implements Base { private final String value; @@ -1453,6 +1464,7 @@ public String toString() { @JsonTypeName("u10") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U10Wrapper implements Base { private final String value; @@ -1499,6 +1511,7 @@ public String toString() { @JsonTypeName("u11") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U11Wrapper implements Base { private final String value; @@ -1545,6 +1558,7 @@ public String toString() { @JsonTypeName("u12") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U12Wrapper implements Base { private final String value; @@ -1591,6 +1605,7 @@ public String toString() { @JsonTypeName("u13") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U13Wrapper implements Base { private final String value; @@ -1637,6 +1652,7 @@ public String toString() { @JsonTypeName("u14") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U14Wrapper implements Base { private final String value; @@ -1683,6 +1699,7 @@ public String toString() { @JsonTypeName("u15") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U15Wrapper implements Base { private final String value; @@ -1729,6 +1746,7 @@ public String toString() { @JsonTypeName("u16") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U16Wrapper implements Base { private final String value; @@ -1775,6 +1793,7 @@ public String toString() { @JsonTypeName("u17") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U17Wrapper implements Base { private final String value; @@ -1821,6 +1840,7 @@ public String toString() { @JsonTypeName("u18") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U18Wrapper implements Base { private final String value; @@ -1867,6 +1887,7 @@ public String toString() { @JsonTypeName("u19") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U19Wrapper implements Base { private final String value; @@ -1913,6 +1934,7 @@ public String toString() { @JsonTypeName("u20") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U20Wrapper implements Base { private final String value; @@ -1959,6 +1981,7 @@ public String toString() { @JsonTypeName("u21") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U21Wrapper implements Base { private final String value; @@ -2005,6 +2028,7 @@ public String toString() { @JsonTypeName("u22") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U22Wrapper implements Base { private final String value; @@ -2051,6 +2075,7 @@ public String toString() { @JsonTypeName("u23") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U23Wrapper implements Base { private final String value; @@ -2097,6 +2122,7 @@ public String toString() { @JsonTypeName("u24") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U24Wrapper implements Base { private final String value; @@ -2143,6 +2169,7 @@ public String toString() { @JsonTypeName("u25") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U25Wrapper implements Base { private final String value; @@ -2189,6 +2216,7 @@ public String toString() { @JsonTypeName("u26") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U26Wrapper implements Base { private final String value; @@ -2235,6 +2263,7 @@ public String toString() { @JsonTypeName("u27") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U27Wrapper implements Base { private final String value; @@ -2281,6 +2310,7 @@ public String toString() { @JsonTypeName("u28") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U28Wrapper implements Base { private final String value; @@ -2327,6 +2357,7 @@ public String toString() { @JsonTypeName("u29") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U29Wrapper implements Base { private final String value; @@ -2373,6 +2404,7 @@ public String toString() { @JsonTypeName("u30") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U30Wrapper implements Base { private final String value; @@ -2419,6 +2451,7 @@ public String toString() { @JsonTypeName("u31") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U31Wrapper implements Base { private final String value; @@ -2465,6 +2498,7 @@ public String toString() { @JsonTypeName("u32") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U32Wrapper implements Base { private final String value; @@ -2511,6 +2545,7 @@ public String toString() { @JsonTypeName("u33") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U33Wrapper implements Base { private final String value; @@ -2557,6 +2592,7 @@ public String toString() { @JsonTypeName("u34") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U34Wrapper implements Base { private final String value; @@ -2603,6 +2639,7 @@ public String toString() { @JsonTypeName("u35") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U35Wrapper implements Base { private final String value; @@ -2649,6 +2686,7 @@ public String toString() { @JsonTypeName("u36") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U36Wrapper implements Base { private final String value; @@ -2695,6 +2733,7 @@ public String toString() { @JsonTypeName("u37") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U37Wrapper implements Base { private final String value; @@ -2741,6 +2780,7 @@ public String toString() { @JsonTypeName("u38") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U38Wrapper implements Base { private final String value; @@ -2787,6 +2827,7 @@ public String toString() { @JsonTypeName("u39") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U39Wrapper implements Base { private final String value; @@ -2833,6 +2874,7 @@ public String toString() { @JsonTypeName("u40") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U40Wrapper implements Base { private final String value; @@ -2879,6 +2921,7 @@ public String toString() { @JsonTypeName("u41") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U41Wrapper implements Base { private final String value; @@ -2925,6 +2968,7 @@ public String toString() { @JsonTypeName("u42") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U42Wrapper implements Base { private final String value; @@ -2971,6 +3015,7 @@ public String toString() { @JsonTypeName("u43") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U43Wrapper implements Base { private final String value; @@ -3017,6 +3062,7 @@ public String toString() { @JsonTypeName("u44") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U44Wrapper implements Base { private final String value; @@ -3063,6 +3109,7 @@ public String toString() { @JsonTypeName("u45") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U45Wrapper implements Base { private final String value; @@ -3109,6 +3156,7 @@ public String toString() { @JsonTypeName("u46") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U46Wrapper implements Base { private final String value; @@ -3155,6 +3203,7 @@ public String toString() { @JsonTypeName("u47") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U47Wrapper implements Base { private final String value; @@ -3201,6 +3250,7 @@ public String toString() { @JsonTypeName("u48") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U48Wrapper implements Base { private final String value; @@ -3247,6 +3297,7 @@ public String toString() { @JsonTypeName("u49") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U49Wrapper implements Base { private final String value; @@ -3293,6 +3344,7 @@ public String toString() { @JsonTypeName("u50") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U50Wrapper implements Base { private final String value; @@ -3339,6 +3391,7 @@ public String toString() { @JsonTypeName("u51") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U51Wrapper implements Base { private final String value; @@ -3385,6 +3438,7 @@ public String toString() { @JsonTypeName("u52") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U52Wrapper implements Base { private final String value; @@ -3431,6 +3485,7 @@ public String toString() { @JsonTypeName("u53") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U53Wrapper implements Base { private final String value; @@ -3477,6 +3532,7 @@ public String toString() { @JsonTypeName("u54") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U54Wrapper implements Base { private final String value; @@ -3523,6 +3579,7 @@ public String toString() { @JsonTypeName("u55") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U55Wrapper implements Base { private final String value; @@ -3569,6 +3626,7 @@ public String toString() { @JsonTypeName("u56") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U56Wrapper implements Base { private final String value; @@ -3615,6 +3673,7 @@ public String toString() { @JsonTypeName("u57") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U57Wrapper implements Base { private final String value; @@ -3661,6 +3720,7 @@ public String toString() { @JsonTypeName("u58") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U58Wrapper implements Base { private final String value; @@ -3707,6 +3767,7 @@ public String toString() { @JsonTypeName("u59") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U59Wrapper implements Base { private final String value; @@ -3753,6 +3814,7 @@ public String toString() { @JsonTypeName("u60") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U60Wrapper implements Base { private final String value; @@ -3799,6 +3861,7 @@ public String toString() { @JsonTypeName("u61") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U61Wrapper implements Base { private final String value; @@ -3845,6 +3908,7 @@ public String toString() { @JsonTypeName("u62") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U62Wrapper implements Base { private final String value; @@ -3891,6 +3955,7 @@ public String toString() { @JsonTypeName("u63") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U63Wrapper implements Base { private final String value; @@ -3937,6 +4002,7 @@ public String toString() { @JsonTypeName("u64") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U64Wrapper implements Base { private final String value; @@ -3983,6 +4049,7 @@ public String toString() { @JsonTypeName("u65") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U65Wrapper implements Base { private final String value; @@ -4029,6 +4096,7 @@ public String toString() { @JsonTypeName("u66") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U66Wrapper implements Base { private final String value; @@ -4075,6 +4143,7 @@ public String toString() { @JsonTypeName("u67") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U67Wrapper implements Base { private final String value; @@ -4121,6 +4190,7 @@ public String toString() { @JsonTypeName("u68") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U68Wrapper implements Base { private final String value; @@ -4167,6 +4237,7 @@ public String toString() { @JsonTypeName("u69") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U69Wrapper implements Base { private final String value; @@ -4213,6 +4284,7 @@ public String toString() { @JsonTypeName("u70") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U70Wrapper implements Base { private final String value; @@ -4259,6 +4331,7 @@ public String toString() { @JsonTypeName("u71") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U71Wrapper implements Base { private final String value; @@ -4305,6 +4378,7 @@ public String toString() { @JsonTypeName("u72") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U72Wrapper implements Base { private final String value; @@ -4351,6 +4425,7 @@ public String toString() { @JsonTypeName("u73") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U73Wrapper implements Base { private final String value; @@ -4397,6 +4472,7 @@ public String toString() { @JsonTypeName("u74") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U74Wrapper implements Base { private final String value; @@ -4443,6 +4519,7 @@ public String toString() { @JsonTypeName("u75") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U75Wrapper implements Base { private final String value; @@ -4489,6 +4566,7 @@ public String toString() { @JsonTypeName("u76") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U76Wrapper implements Base { private final String value; @@ -4535,6 +4613,7 @@ public String toString() { @JsonTypeName("u77") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U77Wrapper implements Base { private final String value; @@ -4581,6 +4660,7 @@ public String toString() { @JsonTypeName("u78") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U78Wrapper implements Base { private final String value; @@ -4627,6 +4707,7 @@ public String toString() { @JsonTypeName("u79") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U79Wrapper implements Base { private final String value; @@ -4673,6 +4754,7 @@ public String toString() { @JsonTypeName("u80") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U80Wrapper implements Base { private final String value; @@ -4719,6 +4801,7 @@ public String toString() { @JsonTypeName("u81") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U81Wrapper implements Base { private final String value; @@ -4765,6 +4848,7 @@ public String toString() { @JsonTypeName("u82") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U82Wrapper implements Base { private final String value; @@ -4811,6 +4895,7 @@ public String toString() { @JsonTypeName("u83") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U83Wrapper implements Base { private final String value; @@ -4857,6 +4942,7 @@ public String toString() { @JsonTypeName("u84") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U84Wrapper implements Base { private final String value; @@ -4903,6 +4989,7 @@ public String toString() { @JsonTypeName("u85") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U85Wrapper implements Base { private final String value; @@ -4949,6 +5036,7 @@ public String toString() { @JsonTypeName("u86") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U86Wrapper implements Base { private final String value; @@ -4995,6 +5083,7 @@ public String toString() { @JsonTypeName("u87") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U87Wrapper implements Base { private final String value; @@ -5041,6 +5130,7 @@ public String toString() { @JsonTypeName("u88") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U88Wrapper implements Base { private final String value; @@ -5087,6 +5177,7 @@ public String toString() { @JsonTypeName("u89") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U89Wrapper implements Base { private final String value; @@ -5133,6 +5224,7 @@ public String toString() { @JsonTypeName("u90") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U90Wrapper implements Base { private final String value; @@ -5179,6 +5271,7 @@ public String toString() { @JsonTypeName("u91") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U91Wrapper implements Base { private final String value; @@ -5225,6 +5318,7 @@ public String toString() { @JsonTypeName("u92") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U92Wrapper implements Base { private final String value; @@ -5271,6 +5365,7 @@ public String toString() { @JsonTypeName("u93") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U93Wrapper implements Base { private final String value; @@ -5317,6 +5412,7 @@ public String toString() { @JsonTypeName("u94") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U94Wrapper implements Base { private final String value; @@ -5363,6 +5459,7 @@ public String toString() { @JsonTypeName("u95") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U95Wrapper implements Base { private final String value; @@ -5409,6 +5506,7 @@ public String toString() { @JsonTypeName("u96") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U96Wrapper implements Base { private final String value; @@ -5455,6 +5553,7 @@ public String toString() { @JsonTypeName("u97") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U97Wrapper implements Base { private final String value; @@ -5501,6 +5600,7 @@ public String toString() { @JsonTypeName("u98") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U98Wrapper implements Base { private final String value; @@ -5547,6 +5647,7 @@ public String toString() { @JsonTypeName("u99") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U99Wrapper implements Base { private final String value; @@ -5593,6 +5694,7 @@ public String toString() { @JsonTypeName("u100") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U100Wrapper implements Base { private final String value; @@ -5637,6 +5739,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -6001,13 +6104,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static LargeUnionExample deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java index c09b59c23..36437c700 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -170,6 +171,7 @@ private interface Base { @JsonTypeName("foo") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class FooWrapper implements Base { private final String value; @@ -214,6 +216,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -376,13 +379,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static SingleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java index f06b6c2b9..ccd537b10 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -247,6 +248,7 @@ private interface Base { @JsonTypeName("foo") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class FooWrapper implements Base { private final String value; @@ -293,6 +295,7 @@ public String toString() { @JsonTypeName("bar") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BarWrapper implements Base { private final int value; @@ -340,6 +343,7 @@ public String toString() { @JsonTypeName("baz") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BazWrapper implements Base { private final long value; @@ -385,6 +389,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -548,13 +553,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static Union deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java index d8801954f..abac2f900 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -710,6 +711,7 @@ private interface Base { @JsonTypeName("stringExample") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class StringExampleWrapper implements Base { private final StringExample value; @@ -756,6 +758,7 @@ public String toString() { @JsonTypeName("thisFieldIsAnInteger") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ThisFieldIsAnIntegerWrapper implements Base { private final int value; @@ -803,6 +806,7 @@ public String toString() { @JsonTypeName("alsoAnInteger") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class AlsoAnIntegerWrapper implements Base { private final int value; @@ -849,6 +853,7 @@ public String toString() { @JsonTypeName("if") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class IfWrapper implements Base { private final int value; @@ -895,6 +900,7 @@ public String toString() { @JsonTypeName("new") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class NewWrapper implements Base { private final int value; @@ -941,6 +947,7 @@ public String toString() { @JsonTypeName("interface") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class InterfaceWrapper implements Base { private final int value; @@ -987,6 +994,7 @@ public String toString() { @JsonTypeName("completed") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class CompletedWrapper implements Base { private final int value; @@ -1033,6 +1041,7 @@ public String toString() { @JsonTypeName("unknown") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class Unknown_Wrapper implements Base { private final int value; @@ -1079,6 +1088,7 @@ public String toString() { @JsonTypeName("optional") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class OptionalWrapper implements Base { private final Optional value; @@ -1126,6 +1136,7 @@ public String toString() { @JsonTypeName("list") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ListWrapper implements Base { private final List value; @@ -1172,6 +1183,7 @@ public String toString() { @JsonTypeName("set") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SetWrapper implements Base { private final Set value; @@ -1220,6 +1232,7 @@ public String toString() { @JsonTypeName("map") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class MapWrapper implements Base { private final Map value; @@ -1266,6 +1279,7 @@ public String toString() { @JsonTypeName("optionalAlias") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class OptionalAliasWrapper implements Base { private final OptionalAlias value; @@ -1313,6 +1327,7 @@ public String toString() { @JsonTypeName("listAlias") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ListAliasWrapper implements Base { private final ListAlias value; @@ -1359,6 +1374,7 @@ public String toString() { @JsonTypeName("setAlias") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SetAliasWrapper implements Base { private final SetAlias value; @@ -1405,6 +1421,7 @@ public String toString() { @JsonTypeName("mapAlias") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class MapAliasWrapper implements Base { private final MapAliasExample value; @@ -1452,6 +1469,7 @@ public String toString() { @JsonTypeName("booleanField") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BooleanFieldWrapper implements Base { private final boolean value; @@ -1498,6 +1516,7 @@ public String toString() { @JsonTypeName("safeInt") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SafeIntWrapper implements Base { private final int value; @@ -1544,6 +1563,7 @@ public String toString() { @JsonTypeName("unsafeDouble") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class UnsafeDoubleWrapper implements Base { private final double value; @@ -1588,6 +1608,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -1788,13 +1809,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static UnionTypeExample deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java index d82cc68f0..a43901f0e 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -171,6 +172,7 @@ private interface Base { @JsonTypeName("unknown") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class Unknown_Wrapper implements Base { private final String value; @@ -215,6 +217,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -379,13 +382,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static UnionWithUnknownString deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java index 3b7c0e65c..a75dedae0 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -230,6 +231,7 @@ private interface Base { @JsonTypeName("list") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ListWrapper implements Base { private final List value; @@ -276,6 +278,7 @@ public String toString() { @JsonTypeName("primitiveList") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class PrimitiveListWrapper implements Base { private final List value; @@ -323,6 +326,7 @@ public String toString() { @JsonTypeName("listOptional") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ListOptionalWrapper implements Base { private final List> value; @@ -368,6 +372,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -537,13 +542,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static ExampleDefensiveCollectionListsUnion deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java index 8fab2139f..1f2f8bb04 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -18,6 +19,7 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureMapDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -200,15 +202,26 @@ private interface Base { @JsonTypeName("map") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class MapWrapper implements Base { private final Map value; + private MapWrapper(Map value) { + this(value, false); + } + + private MapWrapper(Map value, boolean owned) { + Preconditions.checkNotNull(value, "map cannot be null"); + this.value = Collections.unmodifiableMap(owned ? value : new LinkedHashMap<>(value)); + } + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private MapWrapper( - @JsonSetter(value = "map", nulls = Nulls.AS_EMPTY, contentNulls = Nulls.FAIL) @Nonnull + private static MapWrapper fromJson( + @JsonSetter(value = "map", nulls = Nulls.AS_EMPTY, contentNulls = Nulls.FAIL) + @Nonnull + @JsonDeserialize(using = ConjureMapDeserializer.class) Map value) { - Preconditions.checkNotNull(value, "map cannot be null"); - this.value = Collections.unmodifiableMap(new LinkedHashMap<>(value)); + return new MapWrapper(value, true); } @JsonProperty(value = "type", index = 0) @@ -248,15 +261,26 @@ public String toString() { @JsonTypeName("mapOptional") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class MapOptionalWrapper implements Base { private final Map> value; + private MapOptionalWrapper(Map> value) { + this(value, false); + } + + private MapOptionalWrapper(Map> value, boolean owned) { + Preconditions.checkNotNull(value, "mapOptional cannot be null"); + this.value = Collections.unmodifiableMap(owned ? value : new LinkedHashMap<>(value)); + } + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private MapOptionalWrapper( - @JsonSetter(value = "mapOptional", nulls = Nulls.AS_EMPTY, contentNulls = Nulls.AS_EMPTY) @Nonnull + private static MapOptionalWrapper fromJson( + @JsonSetter(value = "mapOptional", nulls = Nulls.AS_EMPTY, contentNulls = Nulls.AS_EMPTY) + @Nonnull + @JsonDeserialize(using = ConjureMapDeserializer.class) Map> value) { - Preconditions.checkNotNull(value, "mapOptional cannot be null"); - this.value = Collections.unmodifiableMap(new LinkedHashMap<>(value)); + return new MapOptionalWrapper(value, true); } @JsonProperty(value = "type", index = 0) @@ -294,6 +318,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -461,13 +486,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static ExampleDefensiveCollectionMapsUnion deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java index ae9063130..56ff5d444 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -168,6 +169,7 @@ private interface Base { @JsonTypeName("set") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SetWrapper implements Base { private final Set value; @@ -214,6 +216,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -380,13 +383,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static ExampleDefensiveCollectionSetsUnion deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java index 068d6ab6a..a7c10190b 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -230,6 +231,7 @@ private interface Base { @JsonTypeName("list") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ListWrapper implements Base { private final List value; @@ -276,6 +278,7 @@ public String toString() { @JsonTypeName("primitiveList") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class PrimitiveListWrapper implements Base { private final List value; @@ -323,6 +326,7 @@ public String toString() { @JsonTypeName("listOptional") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ListOptionalWrapper implements Base { private final List> value; @@ -368,6 +372,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -537,13 +542,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static ExampleDefensiveCollectionListsUnion deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java index e9da69a42..e50cd1852 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -18,6 +19,7 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureMapDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -200,13 +202,26 @@ private interface Base { @JsonTypeName("map") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class MapWrapper implements Base { private final Map value; - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private MapWrapper(@JsonSetter(value = "map", nulls = Nulls.AS_EMPTY) @Nonnull Map value) { + private MapWrapper(Map value) { + this(value, false); + } + + private MapWrapper(Map value, boolean owned) { Preconditions.checkNotNull(value, "map cannot be null"); - this.value = Collections.unmodifiableMap(new LinkedHashMap<>(value)); + this.value = Collections.unmodifiableMap(owned ? value : new LinkedHashMap<>(value)); + } + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private static MapWrapper fromJson( + @JsonSetter(value = "map", nulls = Nulls.AS_EMPTY) + @Nonnull + @JsonDeserialize(using = ConjureMapDeserializer.class) + Map value) { + return new MapWrapper(value, true); } @JsonProperty(value = "type", index = 0) @@ -246,15 +261,26 @@ public String toString() { @JsonTypeName("mapOptional") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class MapOptionalWrapper implements Base { private final Map> value; + private MapOptionalWrapper(Map> value) { + this(value, false); + } + + private MapOptionalWrapper(Map> value, boolean owned) { + Preconditions.checkNotNull(value, "mapOptional cannot be null"); + this.value = Collections.unmodifiableMap(owned ? value : new LinkedHashMap<>(value)); + } + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private MapOptionalWrapper( - @JsonSetter(value = "mapOptional", nulls = Nulls.AS_EMPTY) @Nonnull + private static MapOptionalWrapper fromJson( + @JsonSetter(value = "mapOptional", nulls = Nulls.AS_EMPTY) + @Nonnull + @JsonDeserialize(using = ConjureMapDeserializer.class) Map> value) { - Preconditions.checkNotNull(value, "mapOptional cannot be null"); - this.value = Collections.unmodifiableMap(new LinkedHashMap<>(value)); + return new MapOptionalWrapper(value, true); } @JsonProperty(value = "type", index = 0) @@ -292,6 +318,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -459,13 +486,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static ExampleDefensiveCollectionMapsUnion deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java index 76b8e2c0b..d666fe95c 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -168,6 +169,7 @@ private interface Base { @JsonTypeName("set") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SetWrapper implements Base { private final Set value; @@ -214,6 +216,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -380,13 +383,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static ExampleDefensiveCollectionSetsUnion deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java index 82c2b50e4..28ae9b367 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -46,6 +47,7 @@ public static EmptyUnion unknown(@Safe String type, Object value) { public abstract T accept(Visitor visitor); + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Unknown extends EmptyUnion { @@ -216,13 +218,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java index 582f875a5..68c9336fc 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.core.JsonGenerator; @@ -85,6 +86,7 @@ public sealed interface Known permits Foo, Bar, Baz {} @JsonTypeName("foo") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Foo extends SimpleUnion implements Known { @@ -133,6 +135,7 @@ public String toString() { @JsonTypeName("bar") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Bar extends SimpleUnion implements Known { @@ -181,6 +184,7 @@ public String toString() { @JsonTypeName("baz") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Baz extends SimpleUnion implements Known { @@ -227,6 +231,7 @@ public String toString() { } } + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Unknown extends SimpleUnion { @@ -401,13 +406,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java index e2f22ef7e..256632839 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.Nulls; @@ -107,6 +108,7 @@ public sealed interface Known @JsonTypeName("stringVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class StringVariant extends UnionExample implements Known { @@ -155,6 +157,7 @@ public String toString() { @JsonTypeName("intVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class IntVariant extends UnionExample implements Known { @@ -203,6 +206,7 @@ public String toString() { @JsonTypeName("objectVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class ObjectVariant extends UnionExample implements Known { @@ -251,6 +255,7 @@ public String toString() { @JsonTypeName("collectionVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class CollectionVariant extends UnionExample implements Known { @@ -300,6 +305,7 @@ public String toString() { @JsonTypeName("optionalVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class OptionalVariant extends UnionExample implements Known { @@ -347,6 +353,7 @@ public String toString() { } } + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Unknown extends UnionExample { @@ -525,13 +532,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java index 1818aa7fb..f719adba1 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -127,6 +128,7 @@ private interface Base { T accept(Visitor visitor); } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -287,13 +289,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java index 8ae9cc4e5..b313b2a5a 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -286,6 +287,7 @@ private interface Base { @JsonTypeName("stringVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class StringVariantWrapper implements Base { private final String value; @@ -332,6 +334,7 @@ public String toString() { @JsonTypeName("intVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class IntVariantWrapper implements Base { private final int value; @@ -378,6 +381,7 @@ public String toString() { @JsonTypeName("objectVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ObjectVariantWrapper implements Base { private final ObjectReference value; @@ -424,6 +428,7 @@ public String toString() { @JsonTypeName("collectionVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class CollectionVariantWrapper implements Base { private final List value; @@ -472,6 +477,7 @@ public String toString() { @JsonTypeName("optionalVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class OptionalVariantWrapper implements Base { private final Optional value; @@ -518,6 +524,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -690,13 +697,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java index 46cbfb138..76709c5ae 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -127,6 +128,7 @@ private interface Base { T accept(Visitor visitor); } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -287,13 +289,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java index 59396b06a..39ebf739b 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -222,6 +223,7 @@ private interface Base { @JsonTypeName("foo") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class FooWrapper implements Base { private final String value; @@ -268,6 +270,7 @@ public String toString() { @JsonTypeName("bar") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BarWrapper implements Base { private final int value; @@ -314,6 +317,7 @@ public String toString() { @JsonTypeName("baz") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BazWrapper implements Base { private final SafeLong value; @@ -358,6 +362,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -523,13 +528,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java index 3ffb0c54e..049536dda 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -286,6 +287,7 @@ private interface Base { @JsonTypeName("stringVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class StringVariantWrapper implements Base { private final String value; @@ -332,6 +334,7 @@ public String toString() { @JsonTypeName("intVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class IntVariantWrapper implements Base { private final int value; @@ -378,6 +381,7 @@ public String toString() { @JsonTypeName("objectVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ObjectVariantWrapper implements Base { private final ObjectReference value; @@ -424,6 +428,7 @@ public String toString() { @JsonTypeName("collectionVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class CollectionVariantWrapper implements Base { private final List value; @@ -472,6 +477,7 @@ public String toString() { @JsonTypeName("optionalVariant") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class OptionalVariantWrapper implements Base { private final Optional value; @@ -518,6 +524,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -690,13 +697,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java b/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java index 5461295a9..9f3f09a30 100644 --- a/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java +++ b/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -171,6 +172,7 @@ private interface Base { @JsonTypeName("field") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class FieldWrapper implements Base { private final String value; @@ -215,6 +217,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -377,13 +380,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static UnionWithExternal deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java index dc3405254..a02a79ba1 100644 --- a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -222,6 +223,7 @@ private interface Base { @JsonTypeName("foo") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class FooWrapper implements Base { private final String value; @@ -268,6 +270,7 @@ public String toString() { @JsonTypeName("bar") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BarWrapper implements Base { private final int value; @@ -314,6 +317,7 @@ public String toString() { @JsonTypeName("baz") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BazWrapper implements Base { private final SafeLong value; @@ -358,6 +362,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -523,13 +528,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java index fcd3a9c0f..13aa07b86 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.core.JsonGenerator; @@ -70,6 +71,7 @@ public sealed interface Known permits CamelCasedField {} @JsonTypeName("camelCasedField") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class CamelCasedField extends CamelCaseUnion implements Known { @@ -116,6 +118,7 @@ public String toString() { } } + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Unknown extends CamelCaseUnion { @@ -288,13 +291,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static CamelCaseUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java index 5aaba2005..51d823bfa 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -47,6 +48,7 @@ public static EmptyUnion unknown(@Safe String type, Object value) { public abstract T accept(Visitor visitor); + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Unknown extends EmptyUnion { @@ -217,13 +219,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java index 1bc08e24b..d4e9c9d92 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.core.JsonGenerator; @@ -70,6 +71,7 @@ public sealed interface Known permits Empty {} @JsonTypeName("empty") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Empty extends NestedEmptyUnion implements Known { @@ -116,6 +118,7 @@ public String toString() { } } + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Unknown extends NestedEmptyUnion { @@ -288,13 +291,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static NestedEmptyUnion deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java index e0c037959..d121a7a4c 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.core.JsonGenerator; @@ -86,6 +87,7 @@ public sealed interface Known permits Foo, Bar, Baz {} @JsonTypeName("foo") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Foo extends SimpleUnion implements Known { @@ -134,6 +136,7 @@ public String toString() { @JsonTypeName("bar") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Bar extends SimpleUnion implements Known { @@ -182,6 +185,7 @@ public String toString() { @JsonTypeName("baz") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Baz extends SimpleUnion implements Known { @@ -228,6 +232,7 @@ public String toString() { } } + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Unknown extends SimpleUnion { @@ -402,13 +407,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java index f0fe99fc6..db41562af 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.core.JsonGenerator; @@ -199,6 +200,7 @@ public sealed interface Known @JsonTypeName("known") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Known_ extends UnionReservedNames implements Known { @@ -247,6 +249,7 @@ public String toString() { @JsonTypeName("unknown") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Unknown_ extends UnionReservedNames implements Known { @@ -295,6 +298,7 @@ public String toString() { @JsonTypeName("if") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class If extends UnionReservedNames implements Known { @@ -343,6 +347,7 @@ public String toString() { @JsonTypeName("new") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class New extends UnionReservedNames implements Known { @@ -391,6 +396,7 @@ public String toString() { @JsonTypeName("interface") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Interface extends UnionReservedNames implements Known { @@ -439,6 +445,7 @@ public String toString() { @JsonTypeName("void") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Void extends UnionReservedNames implements Known { @@ -487,6 +494,7 @@ public String toString() { @JsonTypeName("return") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Return extends UnionReservedNames implements Known { @@ -535,6 +543,7 @@ public String toString() { @JsonTypeName("private") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Private extends UnionReservedNames implements Known { @@ -583,6 +592,7 @@ public String toString() { @JsonTypeName("public") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Public extends UnionReservedNames implements Known { @@ -631,6 +641,7 @@ public String toString() { @JsonTypeName("int") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Int extends UnionReservedNames implements Known { @@ -679,6 +690,7 @@ public String toString() { @JsonTypeName("import") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Import extends UnionReservedNames implements Known { @@ -727,6 +739,7 @@ public String toString() { @JsonTypeName("final") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Final extends UnionReservedNames implements Known { @@ -775,6 +788,7 @@ public String toString() { @JsonTypeName("throws") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Throws extends UnionReservedNames implements Known { @@ -823,6 +837,7 @@ public String toString() { @JsonTypeName("static") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Static extends UnionReservedNames implements Known { @@ -871,6 +886,7 @@ public String toString() { @JsonTypeName("unionReservedNames") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class UnionReservedNames_ extends UnionReservedNames implements Known { @@ -917,6 +933,7 @@ public String toString() { } } + @JsonPropertyOrder("type") @JsonDeserialize @JsonSerialize public static final class Unknown extends UnionReservedNames { @@ -1119,13 +1136,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static UnionReservedNames deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java index 91df200b1..f299a8815 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -127,6 +128,7 @@ private interface Base { T accept(Visitor visitor); } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -289,13 +291,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static EmptyUnionTypeExample deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java index 0855d074f..9ebe56d29 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -163,6 +164,7 @@ private interface Base { @JsonTypeName("safeLong") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SafeLongWrapper implements Base { private final long value; @@ -207,6 +209,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -372,13 +375,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static ExternalLongUnionExample deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java index ddfdd454a..4f1306e6a 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -993,6 +994,7 @@ private interface Base { @JsonTypeName("u0") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U0Wrapper implements Base { private final String value; @@ -1039,6 +1041,7 @@ public String toString() { @JsonTypeName("u1") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U1Wrapper implements Base { private final String value; @@ -1085,6 +1088,7 @@ public String toString() { @JsonTypeName("u2") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U2Wrapper implements Base { private final String value; @@ -1131,6 +1135,7 @@ public String toString() { @JsonTypeName("u3") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U3Wrapper implements Base { private final String value; @@ -1177,6 +1182,7 @@ public String toString() { @JsonTypeName("u4") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U4Wrapper implements Base { private final String value; @@ -1223,6 +1229,7 @@ public String toString() { @JsonTypeName("u5") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U5Wrapper implements Base { private final String value; @@ -1269,6 +1276,7 @@ public String toString() { @JsonTypeName("u6") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U6Wrapper implements Base { private final String value; @@ -1315,6 +1323,7 @@ public String toString() { @JsonTypeName("u7") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U7Wrapper implements Base { private final String value; @@ -1361,6 +1370,7 @@ public String toString() { @JsonTypeName("u8") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U8Wrapper implements Base { private final String value; @@ -1407,6 +1417,7 @@ public String toString() { @JsonTypeName("u9") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U9Wrapper implements Base { private final String value; @@ -1453,6 +1464,7 @@ public String toString() { @JsonTypeName("u10") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U10Wrapper implements Base { private final String value; @@ -1499,6 +1511,7 @@ public String toString() { @JsonTypeName("u11") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U11Wrapper implements Base { private final String value; @@ -1545,6 +1558,7 @@ public String toString() { @JsonTypeName("u12") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U12Wrapper implements Base { private final String value; @@ -1591,6 +1605,7 @@ public String toString() { @JsonTypeName("u13") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U13Wrapper implements Base { private final String value; @@ -1637,6 +1652,7 @@ public String toString() { @JsonTypeName("u14") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U14Wrapper implements Base { private final String value; @@ -1683,6 +1699,7 @@ public String toString() { @JsonTypeName("u15") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U15Wrapper implements Base { private final String value; @@ -1729,6 +1746,7 @@ public String toString() { @JsonTypeName("u16") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U16Wrapper implements Base { private final String value; @@ -1775,6 +1793,7 @@ public String toString() { @JsonTypeName("u17") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U17Wrapper implements Base { private final String value; @@ -1821,6 +1840,7 @@ public String toString() { @JsonTypeName("u18") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U18Wrapper implements Base { private final String value; @@ -1867,6 +1887,7 @@ public String toString() { @JsonTypeName("u19") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U19Wrapper implements Base { private final String value; @@ -1913,6 +1934,7 @@ public String toString() { @JsonTypeName("u20") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U20Wrapper implements Base { private final String value; @@ -1959,6 +1981,7 @@ public String toString() { @JsonTypeName("u21") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U21Wrapper implements Base { private final String value; @@ -2005,6 +2028,7 @@ public String toString() { @JsonTypeName("u22") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U22Wrapper implements Base { private final String value; @@ -2051,6 +2075,7 @@ public String toString() { @JsonTypeName("u23") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U23Wrapper implements Base { private final String value; @@ -2097,6 +2122,7 @@ public String toString() { @JsonTypeName("u24") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U24Wrapper implements Base { private final String value; @@ -2143,6 +2169,7 @@ public String toString() { @JsonTypeName("u25") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U25Wrapper implements Base { private final String value; @@ -2189,6 +2216,7 @@ public String toString() { @JsonTypeName("u26") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U26Wrapper implements Base { private final String value; @@ -2235,6 +2263,7 @@ public String toString() { @JsonTypeName("u27") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U27Wrapper implements Base { private final String value; @@ -2281,6 +2310,7 @@ public String toString() { @JsonTypeName("u28") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U28Wrapper implements Base { private final String value; @@ -2327,6 +2357,7 @@ public String toString() { @JsonTypeName("u29") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U29Wrapper implements Base { private final String value; @@ -2373,6 +2404,7 @@ public String toString() { @JsonTypeName("u30") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U30Wrapper implements Base { private final String value; @@ -2419,6 +2451,7 @@ public String toString() { @JsonTypeName("u31") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U31Wrapper implements Base { private final String value; @@ -2465,6 +2498,7 @@ public String toString() { @JsonTypeName("u32") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U32Wrapper implements Base { private final String value; @@ -2511,6 +2545,7 @@ public String toString() { @JsonTypeName("u33") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U33Wrapper implements Base { private final String value; @@ -2557,6 +2592,7 @@ public String toString() { @JsonTypeName("u34") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U34Wrapper implements Base { private final String value; @@ -2603,6 +2639,7 @@ public String toString() { @JsonTypeName("u35") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U35Wrapper implements Base { private final String value; @@ -2649,6 +2686,7 @@ public String toString() { @JsonTypeName("u36") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U36Wrapper implements Base { private final String value; @@ -2695,6 +2733,7 @@ public String toString() { @JsonTypeName("u37") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U37Wrapper implements Base { private final String value; @@ -2741,6 +2780,7 @@ public String toString() { @JsonTypeName("u38") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U38Wrapper implements Base { private final String value; @@ -2787,6 +2827,7 @@ public String toString() { @JsonTypeName("u39") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U39Wrapper implements Base { private final String value; @@ -2833,6 +2874,7 @@ public String toString() { @JsonTypeName("u40") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U40Wrapper implements Base { private final String value; @@ -2879,6 +2921,7 @@ public String toString() { @JsonTypeName("u41") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U41Wrapper implements Base { private final String value; @@ -2925,6 +2968,7 @@ public String toString() { @JsonTypeName("u42") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U42Wrapper implements Base { private final String value; @@ -2971,6 +3015,7 @@ public String toString() { @JsonTypeName("u43") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U43Wrapper implements Base { private final String value; @@ -3017,6 +3062,7 @@ public String toString() { @JsonTypeName("u44") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U44Wrapper implements Base { private final String value; @@ -3063,6 +3109,7 @@ public String toString() { @JsonTypeName("u45") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U45Wrapper implements Base { private final String value; @@ -3109,6 +3156,7 @@ public String toString() { @JsonTypeName("u46") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U46Wrapper implements Base { private final String value; @@ -3155,6 +3203,7 @@ public String toString() { @JsonTypeName("u47") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U47Wrapper implements Base { private final String value; @@ -3201,6 +3250,7 @@ public String toString() { @JsonTypeName("u48") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U48Wrapper implements Base { private final String value; @@ -3247,6 +3297,7 @@ public String toString() { @JsonTypeName("u49") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U49Wrapper implements Base { private final String value; @@ -3293,6 +3344,7 @@ public String toString() { @JsonTypeName("u50") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U50Wrapper implements Base { private final String value; @@ -3339,6 +3391,7 @@ public String toString() { @JsonTypeName("u51") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U51Wrapper implements Base { private final String value; @@ -3385,6 +3438,7 @@ public String toString() { @JsonTypeName("u52") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U52Wrapper implements Base { private final String value; @@ -3431,6 +3485,7 @@ public String toString() { @JsonTypeName("u53") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U53Wrapper implements Base { private final String value; @@ -3477,6 +3532,7 @@ public String toString() { @JsonTypeName("u54") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U54Wrapper implements Base { private final String value; @@ -3523,6 +3579,7 @@ public String toString() { @JsonTypeName("u55") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U55Wrapper implements Base { private final String value; @@ -3569,6 +3626,7 @@ public String toString() { @JsonTypeName("u56") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U56Wrapper implements Base { private final String value; @@ -3615,6 +3673,7 @@ public String toString() { @JsonTypeName("u57") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U57Wrapper implements Base { private final String value; @@ -3661,6 +3720,7 @@ public String toString() { @JsonTypeName("u58") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U58Wrapper implements Base { private final String value; @@ -3707,6 +3767,7 @@ public String toString() { @JsonTypeName("u59") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U59Wrapper implements Base { private final String value; @@ -3753,6 +3814,7 @@ public String toString() { @JsonTypeName("u60") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U60Wrapper implements Base { private final String value; @@ -3799,6 +3861,7 @@ public String toString() { @JsonTypeName("u61") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U61Wrapper implements Base { private final String value; @@ -3845,6 +3908,7 @@ public String toString() { @JsonTypeName("u62") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U62Wrapper implements Base { private final String value; @@ -3891,6 +3955,7 @@ public String toString() { @JsonTypeName("u63") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U63Wrapper implements Base { private final String value; @@ -3937,6 +4002,7 @@ public String toString() { @JsonTypeName("u64") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U64Wrapper implements Base { private final String value; @@ -3983,6 +4049,7 @@ public String toString() { @JsonTypeName("u65") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U65Wrapper implements Base { private final String value; @@ -4029,6 +4096,7 @@ public String toString() { @JsonTypeName("u66") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U66Wrapper implements Base { private final String value; @@ -4075,6 +4143,7 @@ public String toString() { @JsonTypeName("u67") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U67Wrapper implements Base { private final String value; @@ -4121,6 +4190,7 @@ public String toString() { @JsonTypeName("u68") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U68Wrapper implements Base { private final String value; @@ -4167,6 +4237,7 @@ public String toString() { @JsonTypeName("u69") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U69Wrapper implements Base { private final String value; @@ -4213,6 +4284,7 @@ public String toString() { @JsonTypeName("u70") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U70Wrapper implements Base { private final String value; @@ -4259,6 +4331,7 @@ public String toString() { @JsonTypeName("u71") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U71Wrapper implements Base { private final String value; @@ -4305,6 +4378,7 @@ public String toString() { @JsonTypeName("u72") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U72Wrapper implements Base { private final String value; @@ -4351,6 +4425,7 @@ public String toString() { @JsonTypeName("u73") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U73Wrapper implements Base { private final String value; @@ -4397,6 +4472,7 @@ public String toString() { @JsonTypeName("u74") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U74Wrapper implements Base { private final String value; @@ -4443,6 +4519,7 @@ public String toString() { @JsonTypeName("u75") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U75Wrapper implements Base { private final String value; @@ -4489,6 +4566,7 @@ public String toString() { @JsonTypeName("u76") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U76Wrapper implements Base { private final String value; @@ -4535,6 +4613,7 @@ public String toString() { @JsonTypeName("u77") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U77Wrapper implements Base { private final String value; @@ -4581,6 +4660,7 @@ public String toString() { @JsonTypeName("u78") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U78Wrapper implements Base { private final String value; @@ -4627,6 +4707,7 @@ public String toString() { @JsonTypeName("u79") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U79Wrapper implements Base { private final String value; @@ -4673,6 +4754,7 @@ public String toString() { @JsonTypeName("u80") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U80Wrapper implements Base { private final String value; @@ -4719,6 +4801,7 @@ public String toString() { @JsonTypeName("u81") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U81Wrapper implements Base { private final String value; @@ -4765,6 +4848,7 @@ public String toString() { @JsonTypeName("u82") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U82Wrapper implements Base { private final String value; @@ -4811,6 +4895,7 @@ public String toString() { @JsonTypeName("u83") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U83Wrapper implements Base { private final String value; @@ -4857,6 +4942,7 @@ public String toString() { @JsonTypeName("u84") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U84Wrapper implements Base { private final String value; @@ -4903,6 +4989,7 @@ public String toString() { @JsonTypeName("u85") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U85Wrapper implements Base { private final String value; @@ -4949,6 +5036,7 @@ public String toString() { @JsonTypeName("u86") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U86Wrapper implements Base { private final String value; @@ -4995,6 +5083,7 @@ public String toString() { @JsonTypeName("u87") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U87Wrapper implements Base { private final String value; @@ -5041,6 +5130,7 @@ public String toString() { @JsonTypeName("u88") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U88Wrapper implements Base { private final String value; @@ -5087,6 +5177,7 @@ public String toString() { @JsonTypeName("u89") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U89Wrapper implements Base { private final String value; @@ -5133,6 +5224,7 @@ public String toString() { @JsonTypeName("u90") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U90Wrapper implements Base { private final String value; @@ -5179,6 +5271,7 @@ public String toString() { @JsonTypeName("u91") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U91Wrapper implements Base { private final String value; @@ -5225,6 +5318,7 @@ public String toString() { @JsonTypeName("u92") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U92Wrapper implements Base { private final String value; @@ -5271,6 +5365,7 @@ public String toString() { @JsonTypeName("u93") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U93Wrapper implements Base { private final String value; @@ -5317,6 +5412,7 @@ public String toString() { @JsonTypeName("u94") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U94Wrapper implements Base { private final String value; @@ -5363,6 +5459,7 @@ public String toString() { @JsonTypeName("u95") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U95Wrapper implements Base { private final String value; @@ -5409,6 +5506,7 @@ public String toString() { @JsonTypeName("u96") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U96Wrapper implements Base { private final String value; @@ -5455,6 +5553,7 @@ public String toString() { @JsonTypeName("u97") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U97Wrapper implements Base { private final String value; @@ -5501,6 +5600,7 @@ public String toString() { @JsonTypeName("u98") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U98Wrapper implements Base { private final String value; @@ -5547,6 +5647,7 @@ public String toString() { @JsonTypeName("u99") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U99Wrapper implements Base { private final String value; @@ -5593,6 +5694,7 @@ public String toString() { @JsonTypeName("u100") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class U100Wrapper implements Base { private final String value; @@ -5637,6 +5739,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -6001,13 +6104,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static LargeUnionExample deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java index 8f822d989..e4e12b6d9 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -160,6 +161,7 @@ private interface Base { @JsonTypeName("foo") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class FooWrapper implements Base { private final String value; @@ -204,6 +206,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -366,13 +369,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static SingleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java index ca2c32982..eaa22fd29 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -237,6 +238,7 @@ private interface Base { @JsonTypeName("foo") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class FooWrapper implements Base { private final String value; @@ -283,6 +285,7 @@ public String toString() { @JsonTypeName("bar") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BarWrapper implements Base { private final int value; @@ -330,6 +333,7 @@ public String toString() { @JsonTypeName("baz") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BazWrapper implements Base { private final long value; @@ -375,6 +379,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -538,13 +543,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static Union deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java index 59412e5a5..4afee0217 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -700,6 +701,7 @@ private interface Base { @JsonTypeName("stringExample") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class StringExampleWrapper implements Base { private final StringExample value; @@ -746,6 +748,7 @@ public String toString() { @JsonTypeName("thisFieldIsAnInteger") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ThisFieldIsAnIntegerWrapper implements Base { private final int value; @@ -793,6 +796,7 @@ public String toString() { @JsonTypeName("alsoAnInteger") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class AlsoAnIntegerWrapper implements Base { private final int value; @@ -839,6 +843,7 @@ public String toString() { @JsonTypeName("if") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class IfWrapper implements Base { private final int value; @@ -885,6 +890,7 @@ public String toString() { @JsonTypeName("new") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class NewWrapper implements Base { private final int value; @@ -931,6 +937,7 @@ public String toString() { @JsonTypeName("interface") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class InterfaceWrapper implements Base { private final int value; @@ -977,6 +984,7 @@ public String toString() { @JsonTypeName("completed") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class CompletedWrapper implements Base { private final int value; @@ -1023,6 +1031,7 @@ public String toString() { @JsonTypeName("unknown") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class Unknown_Wrapper implements Base { private final int value; @@ -1069,6 +1078,7 @@ public String toString() { @JsonTypeName("optional") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class OptionalWrapper implements Base { private final Optional value; @@ -1116,6 +1126,7 @@ public String toString() { @JsonTypeName("list") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ListWrapper implements Base { private final List value; @@ -1162,6 +1173,7 @@ public String toString() { @JsonTypeName("set") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SetWrapper implements Base { private final Set value; @@ -1210,6 +1222,7 @@ public String toString() { @JsonTypeName("map") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class MapWrapper implements Base { private final Map value; @@ -1256,6 +1269,7 @@ public String toString() { @JsonTypeName("optionalAlias") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class OptionalAliasWrapper implements Base { private final OptionalAlias value; @@ -1303,6 +1317,7 @@ public String toString() { @JsonTypeName("listAlias") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class ListAliasWrapper implements Base { private final ListAlias value; @@ -1349,6 +1364,7 @@ public String toString() { @JsonTypeName("setAlias") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SetAliasWrapper implements Base { private final SetAlias value; @@ -1395,6 +1411,7 @@ public String toString() { @JsonTypeName("mapAlias") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class MapAliasWrapper implements Base { private final MapAliasExample value; @@ -1442,6 +1459,7 @@ public String toString() { @JsonTypeName("booleanField") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BooleanFieldWrapper implements Base { private final boolean value; @@ -1488,6 +1506,7 @@ public String toString() { @JsonTypeName("safeInt") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class SafeIntWrapper implements Base { private final int value; @@ -1534,6 +1553,7 @@ public String toString() { @JsonTypeName("unsafeDouble") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class UnsafeDoubleWrapper implements Base { private final double value; @@ -1578,6 +1598,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -1778,13 +1799,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static UnionTypeExample deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java index de02a1798..66431a4cc 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -161,6 +162,7 @@ private interface Base { @JsonTypeName("unknown") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class Unknown_Wrapper implements Base { private final String value; @@ -205,6 +207,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -369,13 +372,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static UnionWithUnknownString deserializeUnknown( JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { diff --git a/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java index 154fbb1a4..ab9fb1aa9 100644 --- a/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; @@ -222,6 +223,7 @@ private interface Base { @JsonTypeName("foo") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class FooWrapper implements Base { private final String value; @@ -268,6 +270,7 @@ public String toString() { @JsonTypeName("bar") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BarWrapper implements Base { private final int value; @@ -314,6 +317,7 @@ public String toString() { @JsonTypeName("baz") @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPropertyOrder("type") private static final class BazWrapper implements Base { private final SafeLong value; @@ -358,6 +362,7 @@ public String toString() { } } + @JsonPropertyOrder("type") private static final class UnknownWrapper implements Base { private final String type; @@ -523,13 +528,21 @@ private JsonDeserializer resolveDeserializer(DeserializationContext context, private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) throws IOException { Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; if (parser.currentToken() == JsonToken.START_OBJECT) { parser.nextToken(); } while (parser.currentToken() == JsonToken.FIELD_NAME) { String fieldName = parser.currentName(); parser.nextToken(); - values.put(fieldName, context.readValue(parser, Object.class)); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); parser.nextToken(); } if (parser.currentToken() != JsonToken.END_OBJECT) { From eba08ede66b940534e23cb60c533cae18d21dc81 Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Fri, 25 Sep 2026 10:24:34 -0400 Subject: [PATCH 7/9] Consolidate union serDe --- .../conjure/java/types/UnionGenerator.java | 255 ++---------------- 1 file changed, 22 insertions(+), 233 deletions(-) diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java index 95ecb7007..dd5029072 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/UnionGenerator.java @@ -25,19 +25,10 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import com.google.common.collect.PeekingIterator; @@ -45,6 +36,8 @@ import com.palantir.conjure.java.Options; import com.palantir.conjure.java.lib.internal.ConjureCollections; import com.palantir.conjure.java.lib.internal.ConjureMapDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionSerializer; import com.palantir.conjure.java.util.JavaNameSanitizer; import com.palantir.conjure.java.util.Javadoc; import com.palantir.conjure.java.util.Packages; @@ -85,7 +78,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.DoubleFunction; import java.util.function.Function; @@ -114,7 +106,6 @@ public final class UnionGenerator { private static final String SEALED_KNOWN_INTERFACE = "Known"; private static final String SEALED_UNKNOWN_VARIANT_NAME = "Unknown"; private static final String DESERIALIZER_CLASS_NAME = "Deserializer"; - private static final String SERIALIZER_CLASS_NAME = "Serializer"; public static JavaFile generateUnionType( TypeMapper typeMapper, @@ -153,7 +144,6 @@ public static JavaFile generateUnionType( .simpleName()); } ClassName deserializerClass = unionClass.nestedClass(helperNames.newName(DESERIALIZER_CLASS_NAME)); - ClassName serializerClass = unionClass.nestedClass(helperNames.newName(SERIALIZER_CLASS_NAME)); if (options.sealedUnions()) { ClassName unknownVariant = unionClass.nestedClass(SEALED_UNKNOWN_VARIANT_NAME); @@ -165,7 +155,7 @@ public static JavaFile generateUnionType( .addAnnotations(safety) .addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(UnionGenerator.class)) .addAnnotation(generateJsonDeserialize(deserializerClass)) - .addAnnotation(generateJsonSerialize(serializerClass)) + .addAnnotation(generateJsonSerialize()) .addAnnotation(ignoreUnknownAnnotation()) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT, Modifier.SEALED) .addPermittedSubclasses(typeDef.getUnion().stream() @@ -179,7 +169,6 @@ public static JavaFile generateUnionType( .addTypes(generateWrapperClasses( typeMapper, typesMap, unionClass, visitorClass, typeDef.getUnion(), options)) .addType(generateUnknownWrapper(unionClass, visitorClass, options)) - .addType(generateSerializer(unionClass, serializerClass)) .addType(generateDeserializer(unionClass, deserializerClass, typeDef.getUnion(), options)); typeDef.getDocs().ifPresent(docs -> typeBuilder.addJavadoc("$L", Javadoc.render(docs))); @@ -284,9 +273,9 @@ private static AnnotationSpec generateJsonDeserialize(ClassName deserializerClas .build(); } - private static AnnotationSpec generateJsonSerialize(ClassName serializerClass) { + private static AnnotationSpec generateJsonSerialize() { return AnnotationSpec.builder(JsonSerialize.class) - .addMember("using", "$T.class", serializerClass) + .addMember("using", "$T.class", ConjureUnionSerializer.class) .build(); } @@ -856,19 +845,15 @@ private static Stream sortedStageNameTypePairs(Map memberTypeDefs, Options options) { - TypeSpec.Builder builder = TypeSpec.classBuilder(deserializerClass) + return TypeSpec.classBuilder(deserializerClass) .addModifiers(Modifier.STATIC, Modifier.FINAL) - .superclass(ParameterizedTypeName.get(ClassName.get(JsonDeserializer.class), unionClass)) + .superclass(ParameterizedTypeName.get(ClassName.get(ConjureUnionDeserializer.class), unionClass)) .addField(generateVariantTypesField(unionClass, memberTypeDefs, options)) - .addField(generateVariantDeserializersField()) - .addMethod(generateIsCachableMethod()) - .addMethod(generateDeserializeMethod(unionClass)) - .addMethod(generateDeserializeBufferedMethod(unionClass)) - .addMethod(generateIsTypeFieldMethod()) + .addMethod(MethodSpec.constructorBuilder() + .addStatement("super($T.class, VARIANT_TYPES)", unionClass) + .build()) .addMethod(generateDeserializeSelectedMethod(unionClass, memberTypeDefs, options)) - .addMethod(generateResolveDeserializerMethod()) - .addMethod(generateDeserializeUnknownMethod(unionClass, options)); - return builder.build(); + .build(); } private static FieldSpec generateVariantTypesField( @@ -893,146 +878,11 @@ private static FieldSpec generateVariantTypesField( .build(); } - private static FieldSpec generateVariantDeserializersField() { - TypeName deserializerType = ParameterizedTypeName.get( - ClassName.get(JsonDeserializer.class), WildcardTypeName.subtypeOf(Object.class)); - TypeName cacheType = ParameterizedTypeName.get(ClassName.get(AtomicReferenceArray.class), deserializerType); - return FieldSpec.builder(cacheType, "variantDeserializers", Modifier.PRIVATE, Modifier.FINAL) - .initializer("new $T<>(VARIANT_TYPES.length)", AtomicReferenceArray.class) - .build(); - } - - private static MethodSpec generateIsCachableMethod() { - return MethodSpec.methodBuilder("isCachable") - .addAnnotation(Override.class) - .addModifiers(Modifier.PUBLIC) - .returns(TypeName.BOOLEAN) - .addStatement("return true") - .build(); - } - - private static TypeSpec generateSerializer(ClassName unionClass, ClassName serializerClass) { - return TypeSpec.classBuilder(serializerClass) - .addModifiers(Modifier.STATIC, Modifier.FINAL) - .superclass(ParameterizedTypeName.get(ClassName.get(JsonSerializer.class), unionClass)) - .addMethod(MethodSpec.methodBuilder("serialize") - .addAnnotation(Override.class) - .addModifiers(Modifier.PUBLIC) - .addParameter(unionClass, "value") - .addParameter(JsonGenerator.class, "generator") - .addParameter(SerializerProvider.class, "serializers") - .addException(IOException.class) - .addStatement("serializers.findValueSerializer(value.getClass()).serialize(value, generator," - + " serializers)") - .build()) - .build(); - } - - private static MethodSpec generateDeserializeMethod(ClassName unionClass) { - return MethodSpec.methodBuilder("deserialize") - .addAnnotation(Override.class) - .addModifiers(Modifier.PUBLIC) - .returns(unionClass) - .addParameter(JsonParser.class, "parser") - .addParameter(DeserializationContext.class, "context") - .addException(IOException.class) - .beginControlFlow("if (!parser.isExpectedStartObjectToken())") - .addStatement( - "return context.reportInputMismatch($T.class, $S)", - unionClass, - "Expected a JSON object for union deserialization") - .endControlFlow() - .addStatement( - "boolean acceptCaseInsensitiveProperties = context.isEnabled(" - + "$T.ACCEPT_CASE_INSENSITIVE_PROPERTIES)", - MapperFeature.class) - .addStatement("$T firstToken = parser.nextToken()", JsonToken.class) - .beginControlFlow( - "if (firstToken == $T.FIELD_NAME" - + " && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties))", - JsonToken.class) - .beginControlFlow("if (parser.nextToken() != $T.VALUE_STRING)", JsonToken.class) - .addStatement( - "return context.reportInputMismatch($T.class, $S)", - unionClass, - "Union discriminator 'type' must be a string") - .endControlFlow() - .addStatement("$T type = parser.getText()", String.class) - .addStatement("parser.nextToken()") - .addStatement("return deserializeSelected(parser, context, type)") - .endControlFlow() - .addStatement("return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties)") - .build(); - } - - private static MethodSpec generateDeserializeBufferedMethod(ClassName unionClass) { - return MethodSpec.methodBuilder("deserializeBuffered") - .addModifiers(Modifier.PRIVATE) - .returns(unionClass) - .addParameter(JsonParser.class, "parser") - .addParameter(DeserializationContext.class, "context") - .addParameter(TypeName.BOOLEAN, "acceptCaseInsensitiveProperties") - .addException(IOException.class) - .beginControlFlow("try ($T buffer = context.bufferForInputBuffering(parser))", TokenBuffer.class) - .addStatement("buffer.writeStartObject()") - .addStatement("$T token = parser.currentToken()", JsonToken.class) - .beginControlFlow("while (token == $T.FIELD_NAME)", JsonToken.class) - .addStatement("$T fieldName = parser.currentName()", String.class) - .addStatement("$T valueToken = parser.nextToken()", JsonToken.class) - .beginControlFlow("if (isTypeField(fieldName, acceptCaseInsensitiveProperties))") - .beginControlFlow("if (valueToken != $T.VALUE_STRING)", JsonToken.class) - .addStatement( - "return context.reportInputMismatch($T.class, $S)", - unionClass, - "Union discriminator 'type' must be a string") - .endControlFlow() - .addStatement("$T type = parser.getText()", String.class) - .addStatement("parser.nextToken()") - .beginControlFlow("try ($T bufferedParser = buffer.asParser(parser))", JsonParser.class) - .addStatement( - "$T combinedParser = $T.createFlattened(true, bufferedParser, parser)", - JsonParser.class, - JsonParserSequence.class) - .addStatement("combinedParser.nextToken()") - .addStatement("return deserializeSelected(combinedParser, context, type)") - .endControlFlow() - .endControlFlow() - .addStatement("buffer.writeFieldName(fieldName)") - .addStatement("buffer.copyCurrentStructure(parser)") - .addStatement("token = parser.nextToken()") - .endControlFlow() - .beginControlFlow("if (token != $T.END_OBJECT)", JsonToken.class) - .addStatement( - "return context.reportInputMismatch($T.class, $S)", - unionClass, - "Expected the end of a JSON object while deserializing a union") - .endControlFlow() - .endControlFlow() - .addStatement( - "return context.reportInputMismatch($T.class, $S)", - unionClass, - "Union discriminator 'type' is required") - .build(); - } - - private static MethodSpec generateIsTypeFieldMethod() { - return MethodSpec.methodBuilder("isTypeField") - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .returns(TypeName.BOOLEAN) - .addParameter(String.class, "fieldName") - .addParameter(TypeName.BOOLEAN, "acceptCaseInsensitiveProperties") - .addStatement( - "return $S.equals(fieldName)" - + " || (acceptCaseInsensitiveProperties && $S.equalsIgnoreCase(fieldName))", - "type", - "type") - .build(); - } - private static MethodSpec generateDeserializeSelectedMethod( ClassName unionClass, List memberTypeDefs, Options options) { MethodSpec.Builder builder = MethodSpec.methodBuilder("deserializeSelected") - .addModifiers(Modifier.PRIVATE) + .addAnnotation(Override.class) + .addModifiers(Modifier.PROTECTED) .returns(unionClass) .addParameter(JsonParser.class, "parser") .addParameter(DeserializationContext.class, "context") @@ -1044,85 +894,24 @@ private static MethodSpec generateDeserializeSelectedMethod( } builder.addStatement("default -> -1").addCode("};\n"); builder.beginControlFlow("if (variantIndex < 0)"); - builder.addStatement("return deserializeUnknown(parser, context, type)"); - builder.endControlFlow(); - builder.addStatement("$T deserializer = variantDeserializers.get(variantIndex)", JsonDeserializer.class) - .beginControlFlow("if (deserializer == null)") - .addStatement("deserializer = resolveDeserializer(context, variantIndex)") - .endControlFlow(); if (options.sealedUnions()) { - builder.addStatement("return ($T) deserializer.deserialize(parser, context)", unionClass); + builder.addStatement( + "return new $T(type, deserializeUnknown(parser, context))", + unionClass.nestedClass(SEALED_UNKNOWN_VARIANT_NAME)); } else { builder.addStatement( - "return new $T(($T) deserializer.deserialize(parser, context))", + "return new $T(new $T(type, deserializeUnknown(parser, context)))", unionClass, - unionClass.nestedClass("Base")); + unionClass.nestedClass(UNKNOWN_WRAPPER_CLASS_NAME)); } - return builder.build(); - } - - private static MethodSpec generateResolveDeserializerMethod() { - ParameterizedTypeName deserializerType = ParameterizedTypeName.get( - ClassName.get(JsonDeserializer.class), WildcardTypeName.subtypeOf(Object.class)); - return MethodSpec.methodBuilder("resolveDeserializer") - .addModifiers(Modifier.PRIVATE) - .returns(deserializerType) - .addParameter(DeserializationContext.class, "context") - .addParameter(TypeName.INT, "variantIndex") - .addException(JsonMappingException.class) - .addStatement( - "$T deserializer = context.findContextualValueDeserializer(" - + "context.constructType(VARIANT_TYPES[variantIndex]), null)", - deserializerType) - .beginControlFlow("if (variantDeserializers.compareAndSet(variantIndex, null, deserializer))") - .addStatement("return deserializer") - .endControlFlow() - .addStatement("return variantDeserializers.get(variantIndex)") - .build(); - } - - private static MethodSpec generateDeserializeUnknownMethod(ClassName unionClass, Options options) { - ParameterizedTypeName valueMapType = ParameterizedTypeName.get(Map.class, String.class, Object.class); - MethodSpec.Builder builder = MethodSpec.methodBuilder("deserializeUnknown") - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .returns(unionClass) - .addParameter(JsonParser.class, "parser") - .addParameter(DeserializationContext.class, "context") - .addParameter(String.class, "type") - .addException(IOException.class) - .addStatement("$T values = new $T<>()", valueMapType, HashMap.class) - .addStatement("$T<$T> valueDeserializer = null", JsonDeserializer.class, Object.class) - .beginControlFlow("if (parser.currentToken() == $T.START_OBJECT)", JsonToken.class) - .addStatement("parser.nextToken()") - .endControlFlow() - .beginControlFlow("while (parser.currentToken() == $T.FIELD_NAME)", JsonToken.class) - .addStatement("$T fieldName = parser.currentName()", String.class) - .addStatement("parser.nextToken()") - .beginControlFlow("if (valueDeserializer == null)") - .addStatement( - "valueDeserializer = context.findRootValueDeserializer(context.constructType($T.class))", - Object.class) - .endControlFlow() - .addStatement( - "values.put(fieldName, parser.currentToken() == $T.VALUE_NULL ?" - + " valueDeserializer.getNullValue(context) : valueDeserializer.deserialize(parser," - + " context))", - JsonToken.class) - .addStatement("parser.nextToken()") - .endControlFlow() - .beginControlFlow("if (parser.currentToken() != $T.END_OBJECT)", JsonToken.class) - .addStatement( - "return context.reportInputMismatch($T.class, $S)", - unionClass, - "Expected the end of a JSON object while deserializing a union") - .endControlFlow(); + builder.endControlFlow(); if (options.sealedUnions()) { - builder.addStatement("return new $T(type, values)", unionClass.nestedClass(SEALED_UNKNOWN_VARIANT_NAME)); + builder.addStatement("return ($T) deserializeVariant(parser, context, variantIndex)", unionClass); } else { builder.addStatement( - "return new $T(new $T(type, values))", + "return new $T(($T) deserializeVariant(parser, context, variantIndex))", unionClass, - unionClass.nestedClass(UNKNOWN_WRAPPER_CLASS_NAME)); + unionClass.nestedClass("Base")); } return builder.build(); } From db2b68fd7f136366d31780d98a9bd467f33d0f14 Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Fri, 25 Sep 2026 14:22:58 +0000 Subject: [PATCH 8/9] integrationInput --- .../product/EmptyUnionTypeExample.java | 126 +--------------- .../product/ExternalLongUnionExample.java | 129 +--------------- .../palantir/product/LargeUnionExample.java | 125 +--------------- .../com/palantir/product/SingleUnion.java | 125 +--------------- .../com/palantir/product/Union.java | 123 +--------------- .../palantir/product/UnionTypeExample.java | 125 +--------------- .../product/UnionWithUnknownString.java | 127 +--------------- .../ExampleDefensiveCollectionListsUnion.java | 131 +---------------- .../ExampleDefensiveCollectionMapsUnion.java | 131 +---------------- .../ExampleDefensiveCollectionSetsUnion.java | 131 +---------------- .../ExampleDefensiveCollectionListsUnion.java | 131 +---------------- .../ExampleDefensiveCollectionMapsUnion.java | 131 +---------------- .../ExampleDefensiveCollectionSetsUnion.java | 131 +---------------- .../com/palantir/product/EmptyUnion.java | 138 ++--------------- .../com/palantir/product/SimpleUnion.java | 139 ++---------------- .../com/palantir/product/UnionExample.java | 139 ++---------------- .../com/palantir/product/EmptyUnion.java | 124 +--------------- .../com/palantir/product/UnionExample.java | 125 +--------------- .../com/palantir/product/EmptyUnion.java | 124 +--------------- .../com/palantir/product/SimpleUnion.java | 125 +--------------- .../com/palantir/product/UnionExample.java | 125 +--------------- .../product/external/UnionWithExternal.java | 125 +--------------- .../com/palantir/product/SimpleUnion.java | 125 +--------------- .../com/palantir/product/CamelCaseUnion.java | 139 ++---------------- .../com/palantir/product/EmptyUnion.java | 138 ++--------------- .../palantir/product/NestedEmptyUnion.java | 139 ++---------------- .../com/palantir/product/SimpleUnion.java | 139 ++---------------- .../palantir/product/UnionReservedNames.java | 139 ++---------------- .../product/EmptyUnionTypeExample.java | 126 +--------------- .../product/ExternalLongUnionExample.java | 129 +--------------- .../palantir/product/LargeUnionExample.java | 125 +--------------- .../com/palantir/product/SingleUnion.java | 125 +--------------- .../template/com/palantir/product/Union.java | 123 +--------------- .../palantir/product/UnionTypeExample.java | 125 +--------------- .../product/UnionWithUnknownString.java | 127 +--------------- .../com/palantir/product/SimpleUnion.java | 125 +--------------- 36 files changed, 274 insertions(+), 4380 deletions(-) diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java index 59b0cf7a0..c861e37c4 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/EmptyUnionTypeExample.java @@ -7,14 +7,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -23,7 +18,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -199,130 +193,24 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(EmptyUnionTypeExample.class, VARIANT_TYPES); } @Override - public EmptyUnionTypeExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private EmptyUnionTypeExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(EmptyUnionTypeExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private EmptyUnionTypeExample deserializeSelected( + protected EmptyUnionTypeExample deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new EmptyUnionTypeExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static EmptyUnionTypeExample deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + return new EmptyUnionTypeExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new EmptyUnionTypeExample(new UnknownWrapper(type, values)); + return new EmptyUnionTypeExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java index d4c4fcd98..75b85c166 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/ExternalLongUnionExample.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -26,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -280,82 +274,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {SafeLongWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(ExternalLongUnionExample.class, VARIANT_TYPES); } @Override - public ExternalLongUnionExample deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private ExternalLongUnionExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch( - ExternalLongUnionExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private ExternalLongUnionExample deserializeSelected( + protected ExternalLongUnionExample deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -363,51 +290,9 @@ private ExternalLongUnionExample deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new ExternalLongUnionExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static ExternalLongUnionExample deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, - "Expected the end of a JSON object while deserializing a union"); + return new ExternalLongUnionExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new ExternalLongUnionExample(new UnknownWrapper(type, values)); + return new ExternalLongUnionExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java index 0f5f29e13..3187065cb 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/LargeUnionExample.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; @@ -25,7 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.processing.Generated; @@ -5800,7 +5794,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { U0Wrapper.class, U1Wrapper.class, @@ -5905,76 +5899,12 @@ static final class Deserializer extends JsonDeserializer { U100Wrapper.class }; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(LargeUnionExample.class, VARIANT_TYPES); } @Override - public LargeUnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - LargeUnionExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - LargeUnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private LargeUnionExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - LargeUnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - LargeUnionExample.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(LargeUnionExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private LargeUnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected LargeUnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -6082,50 +6012,9 @@ private LargeUnionExample deserializeSelected(JsonParser parser, Deserialization default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new LargeUnionExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static LargeUnionExample deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - LargeUnionExample.class, "Expected the end of a JSON object while deserializing a union"); + return new LargeUnionExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new LargeUnionExample(new UnknownWrapper(type, values)); + return new LargeUnionExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java index 36437c700..f0f08fd4b 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/SingleUnion.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -26,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -277,79 +271,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(SingleUnion.class, VARIANT_TYPES); } @Override - public SingleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - SingleUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SingleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private SingleUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SingleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SingleUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(SingleUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private SingleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected SingleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -357,50 +287,9 @@ private SingleUnion deserializeSelected(JsonParser parser, DeserializationContex default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new SingleUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static SingleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SingleUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new SingleUnion(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new SingleUnion(new UnknownWrapper(type, values)); + return new SingleUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java index ccd537b10..3e8ce0038 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/Union.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -26,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; @@ -450,78 +444,16 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(Union.class, VARIANT_TYPES); } @Override - public Union deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch(Union.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch(Union.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private Union deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - Union.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - Union.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(Union.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private Union deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected Union deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -531,50 +463,9 @@ private Union deserializeSelected(JsonParser parser, DeserializationContext cont default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new Union((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static Union deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - Union.class, "Expected the end of a JSON object while deserializing a union"); + return new Union(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new Union(new UnknownWrapper(type, values)); + return new Union((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java index abac2f900..f780b34c5 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionTypeExample.java @@ -11,14 +11,9 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -32,7 +27,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.DoubleFunction; import java.util.function.Function; @@ -1669,7 +1663,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringExampleWrapper.class, ThisFieldIsAnIntegerWrapper.class, @@ -1692,76 +1686,12 @@ static final class Deserializer extends JsonDeserializer { UnsafeDoubleWrapper.class }; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(UnionTypeExample.class, VARIANT_TYPES); } @Override - public UnionTypeExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - UnionTypeExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionTypeExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private UnionTypeExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionTypeExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(UnionTypeExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected UnionTypeExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -1787,50 +1717,9 @@ private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationC default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new UnionTypeExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static UnionTypeExample deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + return new UnionTypeExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new UnionTypeExample(new UnknownWrapper(type, values)); + return new UnionTypeExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java index a43901f0e..4bd5b4d78 100644 --- a/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java +++ b/conjure-java-core/src/integrationInput/java/allexamples/com/palantir/product/UnionWithUnknownString.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -26,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -278,81 +272,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Unknown_Wrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(UnionWithUnknownString.class, VARIANT_TYPES); } @Override - public UnionWithUnknownString deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - UnionWithUnknownString.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private UnionWithUnknownString deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionWithUnknownString.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(UnionWithUnknownString.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private UnionWithUnknownString deserializeSelected( + protected UnionWithUnknownString deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -360,50 +288,9 @@ private UnionWithUnknownString deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new UnionWithUnknownString((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static UnionWithUnknownString deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionWithUnknownString.class, "Expected the end of a JSON object while deserializing a union"); + return new UnionWithUnknownString(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new UnionWithUnknownString(new UnknownWrapper(type, values)); + return new UnionWithUnknownString((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java index a75dedae0..175c572fa 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java @@ -11,15 +11,10 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -30,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -433,84 +427,16 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {ListWrapper.class, PrimitiveListWrapper.class, ListOptionalWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(ExampleDefensiveCollectionListsUnion.class, VARIANT_TYPES); } @Override - public ExampleDefensiveCollectionListsUnion deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private ExampleDefensiveCollectionListsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, - "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private ExampleDefensiveCollectionListsUnion deserializeSelected( + protected ExampleDefensiveCollectionListsUnion deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -520,51 +446,10 @@ private ExampleDefensiveCollectionListsUnion deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new ExampleDefensiveCollectionListsUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static ExampleDefensiveCollectionListsUnion deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, - "Expected the end of a JSON object while deserializing a union"); + return new ExampleDefensiveCollectionListsUnion( + new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new ExampleDefensiveCollectionListsUnion(new UnknownWrapper(type, values)); + return new ExampleDefensiveCollectionListsUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java index 1f2f8bb04..b8aac47f2 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java @@ -11,15 +11,10 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureMapDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -30,7 +25,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -379,83 +373,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {MapWrapper.class, MapOptionalWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(ExampleDefensiveCollectionMapsUnion.class, VARIANT_TYPES); } @Override - public ExampleDefensiveCollectionMapsUnion deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private ExampleDefensiveCollectionMapsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, - "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private ExampleDefensiveCollectionMapsUnion deserializeSelected( + protected ExampleDefensiveCollectionMapsUnion deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -464,51 +390,10 @@ private ExampleDefensiveCollectionMapsUnion deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new ExampleDefensiveCollectionMapsUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static ExampleDefensiveCollectionMapsUnion deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, - "Expected the end of a JSON object while deserializing a union"); + return new ExampleDefensiveCollectionMapsUnion( + new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new ExampleDefensiveCollectionMapsUnion(new UnknownWrapper(type, values)); + return new ExampleDefensiveCollectionMapsUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java index 56ff5d444..a5d7bd6dc 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenonnullcollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java @@ -11,15 +11,10 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -30,7 +25,6 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -277,83 +271,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {SetWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(ExampleDefensiveCollectionSetsUnion.class, VARIANT_TYPES); } @Override - public ExampleDefensiveCollectionSetsUnion deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private ExampleDefensiveCollectionSetsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, - "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private ExampleDefensiveCollectionSetsUnion deserializeSelected( + protected ExampleDefensiveCollectionSetsUnion deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -361,51 +287,10 @@ private ExampleDefensiveCollectionSetsUnion deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new ExampleDefensiveCollectionSetsUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static ExampleDefensiveCollectionSetsUnion deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, - "Expected the end of a JSON object while deserializing a union"); + return new ExampleDefensiveCollectionSetsUnion( + new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new ExampleDefensiveCollectionSetsUnion(new UnknownWrapper(type, values)); + return new ExampleDefensiveCollectionSetsUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java index a7c10190b..ee6440413 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionListsUnion.java @@ -11,15 +11,10 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -30,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -433,84 +427,16 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {ListWrapper.class, PrimitiveListWrapper.class, ListOptionalWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(ExampleDefensiveCollectionListsUnion.class, VARIANT_TYPES); } @Override - public ExampleDefensiveCollectionListsUnion deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private ExampleDefensiveCollectionListsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, - "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private ExampleDefensiveCollectionListsUnion deserializeSelected( + protected ExampleDefensiveCollectionListsUnion deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -520,51 +446,10 @@ private ExampleDefensiveCollectionListsUnion deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new ExampleDefensiveCollectionListsUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static ExampleDefensiveCollectionListsUnion deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionListsUnion.class, - "Expected the end of a JSON object while deserializing a union"); + return new ExampleDefensiveCollectionListsUnion( + new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new ExampleDefensiveCollectionListsUnion(new UnknownWrapper(type, values)); + return new ExampleDefensiveCollectionListsUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java index e50cd1852..c56751cd8 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionMapsUnion.java @@ -11,15 +11,10 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureMapDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -30,7 +25,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -379,83 +373,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {MapWrapper.class, MapOptionalWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(ExampleDefensiveCollectionMapsUnion.class, VARIANT_TYPES); } @Override - public ExampleDefensiveCollectionMapsUnion deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private ExampleDefensiveCollectionMapsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, - "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private ExampleDefensiveCollectionMapsUnion deserializeSelected( + protected ExampleDefensiveCollectionMapsUnion deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -464,51 +390,10 @@ private ExampleDefensiveCollectionMapsUnion deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new ExampleDefensiveCollectionMapsUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static ExampleDefensiveCollectionMapsUnion deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionMapsUnion.class, - "Expected the end of a JSON object while deserializing a union"); + return new ExampleDefensiveCollectionMapsUnion( + new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new ExampleDefensiveCollectionMapsUnion(new UnknownWrapper(type, values)); + return new ExampleDefensiveCollectionMapsUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java index d666fe95c..c7ad6ba93 100644 --- a/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java +++ b/conjure-java-core/src/integrationInput/java/defensivenullablecollections/com/palantir/product/ExampleDefensiveCollectionSetsUnion.java @@ -11,15 +11,10 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.internal.ConjureCollections; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -30,7 +25,6 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -277,83 +271,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {SetWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(ExampleDefensiveCollectionSetsUnion.class, VARIANT_TYPES); } @Override - public ExampleDefensiveCollectionSetsUnion deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private ExampleDefensiveCollectionSetsUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, - "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private ExampleDefensiveCollectionSetsUnion deserializeSelected( + protected ExampleDefensiveCollectionSetsUnion deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -361,51 +287,10 @@ private ExampleDefensiveCollectionSetsUnion deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new ExampleDefensiveCollectionSetsUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static ExampleDefensiveCollectionSetsUnion deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExampleDefensiveCollectionSetsUnion.class, - "Expected the end of a JSON object while deserializing a union"); + return new ExampleDefensiveCollectionSetsUnion( + new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new ExampleDefensiveCollectionSetsUnion(new UnknownWrapper(type, values)); + return new ExampleDefensiveCollectionSetsUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java index 28ae9b367..702fa9247 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/EmptyUnion.java @@ -6,19 +6,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionSerializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -27,7 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -35,7 +27,7 @@ @Generated("com.palantir.conjure.java.types.UnionGenerator") @JsonDeserialize(using = EmptyUnion.Deserializer.class) -@JsonSerialize(using = EmptyUnion.Serializer.class) +@JsonSerialize(using = ConjureUnionSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class EmptyUnion permits EmptyUnion.Unknown { public static EmptyUnion unknown(@Safe String type, Object value) { @@ -110,136 +102,24 @@ public String toString() { } } - static final class Serializer extends JsonSerializer { - @Override - public void serialize(EmptyUnion value, JsonGenerator generator, SerializerProvider serializers) - throws IOException { - serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); - } - } - - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(EmptyUnion.class, VARIANT_TYPES); } @Override - public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private EmptyUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - EmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return (EmptyUnion) deserializer.deserialize(parser, context); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new Unknown(type, deserializeUnknown(parser, context)); } - return new Unknown(type, values); + return (EmptyUnion) deserializeVariant(parser, context, variantIndex); } } diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java index 68c9336fc..7db462723 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/SimpleUnion.java @@ -8,20 +8,13 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionSerializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -30,7 +23,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -39,7 +31,7 @@ @Generated("com.palantir.conjure.java.types.UnionGenerator") @JsonDeserialize(using = SimpleUnion.Deserializer.class) -@JsonSerialize(using = SimpleUnion.Serializer.class) +@JsonSerialize(using = ConjureUnionSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class SimpleUnion permits SimpleUnion.Foo, SimpleUnion.Bar, SimpleUnion.Baz, SimpleUnion.Unknown { @@ -294,87 +286,15 @@ public String toString() { } } - static final class Serializer extends JsonSerializer { - @Override - public void serialize(SimpleUnion value, JsonGenerator generator, SerializerProvider serializers) - throws IOException { - serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); - } - } - - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Foo.class, Bar.class, Baz.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(SimpleUnion.class, VARIANT_TYPES); } @Override - public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private SimpleUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -384,50 +304,9 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return (SimpleUnion) deserializer.deserialize(parser, context); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new Unknown(type, deserializeUnknown(parser, context)); } - return new Unknown(type, values); + return (SimpleUnion) deserializeVariant(parser, context, variantIndex); } } diff --git a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java index 256632839..bed156cc5 100644 --- a/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/dialogue/com/palantir/product/UnionExample.java @@ -9,19 +9,12 @@ import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionSerializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -32,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -41,7 +33,7 @@ @Generated("com.palantir.conjure.java.types.UnionGenerator") @JsonDeserialize(using = UnionExample.Deserializer.class) -@JsonSerialize(using = UnionExample.Serializer.class) +@JsonSerialize(using = ConjureUnionSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class UnionExample permits UnionExample.StringVariant, @@ -416,89 +408,17 @@ public String toString() { } } - static final class Serializer extends JsonSerializer { - @Override - public void serialize(UnionExample value, JsonGenerator generator, SerializerProvider serializers) - throws IOException { - serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); - } - } - - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringVariant.class, IntVariant.class, ObjectVariant.class, CollectionVariant.class, OptionalVariant.class }; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(UnionExample.class, VARIANT_TYPES); } @Override - public UnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - UnionExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private UnionExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionExample.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(UnionExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -510,50 +430,9 @@ private UnionExample deserializeSelected(JsonParser parser, DeserializationConte default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return (UnionExample) deserializer.deserialize(parser, context); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionExample.class, "Expected the end of a JSON object while deserializing a union"); + return new Unknown(type, deserializeUnknown(parser, context)); } - return new Unknown(type, values); + return (UnionExample) deserializeVariant(parser, context, variantIndex); } } diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java index f719adba1..f9d48dc22 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/EmptyUnion.java @@ -7,14 +7,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -23,7 +18,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -189,128 +183,24 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(EmptyUnion.class, VARIANT_TYPES); } @Override - public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private EmptyUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - EmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new EmptyUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new EmptyUnion(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new EmptyUnion(new UnknownWrapper(type, values)); + return new EmptyUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java index b313b2a5a..de2d0ee71 100644 --- a/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/errors/com/palantir/product/UnionExample.java @@ -11,14 +11,9 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -29,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -585,7 +579,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringVariantWrapper.class, IntVariantWrapper.class, @@ -594,76 +588,12 @@ static final class Deserializer extends JsonDeserializer { OptionalVariantWrapper.class }; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(UnionExample.class, VARIANT_TYPES); } @Override - public UnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - UnionExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private UnionExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionExample.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(UnionExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -675,50 +605,9 @@ private UnionExample deserializeSelected(JsonParser parser, DeserializationConte default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new UnionExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionExample.class, "Expected the end of a JSON object while deserializing a union"); + return new UnionExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new UnionExample(new UnknownWrapper(type, values)); + return new UnionExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java index 76709c5ae..dcbc42acf 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/EmptyUnion.java @@ -7,14 +7,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -23,7 +18,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -189,128 +183,24 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(EmptyUnion.class, VARIANT_TYPES); } @Override - public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private EmptyUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - EmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new EmptyUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new EmptyUnion(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new EmptyUnion(new UnknownWrapper(type, values)); + return new EmptyUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java index 39ebf739b..158a73b56 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/SimpleUnion.java @@ -10,15 +10,10 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -27,7 +22,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -423,80 +417,16 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(SimpleUnion.class, VARIANT_TYPES); } @Override - public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private SimpleUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -506,50 +436,9 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new SimpleUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new SimpleUnion(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new SimpleUnion(new UnknownWrapper(type, values)); + return new SimpleUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java index 049536dda..39bc64d6f 100644 --- a/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java +++ b/conjure-java-core/src/integrationInput/java/exceptionthrowingdialogueinterfaces/com/palantir/product/UnionExample.java @@ -11,14 +11,9 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -29,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -585,7 +579,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringVariantWrapper.class, IntVariantWrapper.class, @@ -594,76 +588,12 @@ static final class Deserializer extends JsonDeserializer { OptionalVariantWrapper.class }; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(UnionExample.class, VARIANT_TYPES); } @Override - public UnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - UnionExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private UnionExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionExample.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(UnionExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected UnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -675,50 +605,9 @@ private UnionExample deserializeSelected(JsonParser parser, DeserializationConte default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new UnionExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static UnionExample deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionExample.class, "Expected the end of a JSON object while deserializing a union"); + return new UnionExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new UnionExample(new UnknownWrapper(type, values)); + return new UnionExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java b/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java index 9f3f09a30..54edb3990 100644 --- a/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java +++ b/conjure-java-core/src/integrationInput/java/externalfallbacktypes/com/palantir/product/external/UnionWithExternal.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -26,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -278,79 +272,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FieldWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(UnionWithExternal.class, VARIANT_TYPES); } @Override - public UnionWithExternal deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - UnionWithExternal.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionWithExternal.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private UnionWithExternal deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionWithExternal.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionWithExternal.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(UnionWithExternal.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private UnionWithExternal deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected UnionWithExternal deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -358,50 +288,9 @@ private UnionWithExternal deserializeSelected(JsonParser parser, Deserialization default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new UnionWithExternal((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static UnionWithExternal deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionWithExternal.class, "Expected the end of a JSON object while deserializing a union"); + return new UnionWithExternal(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new UnionWithExternal(new UnknownWrapper(type, values)); + return new UnionWithExternal((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java index a02a79ba1..406937630 100644 --- a/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/jersey/com/palantir/product/SimpleUnion.java @@ -10,15 +10,10 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -27,7 +22,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -423,80 +417,16 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(SimpleUnion.class, VARIANT_TYPES); } @Override - public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private SimpleUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -506,50 +436,9 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new SimpleUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new SimpleUnion(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new SimpleUnion(new UnknownWrapper(type, values)); + return new SimpleUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java index 13aa07b86..818f689ba 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/CamelCaseUnion.java @@ -8,19 +8,12 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionSerializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -29,7 +22,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -38,7 +30,7 @@ @Generated("com.palantir.conjure.java.types.UnionGenerator") @JsonDeserialize(using = CamelCaseUnion.Deserializer.class) -@JsonSerialize(using = CamelCaseUnion.Serializer.class) +@JsonSerialize(using = ConjureUnionSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class CamelCaseUnion permits CamelCaseUnion.CamelCasedField, CamelCaseUnion.Unknown { public static CamelCaseUnion camelCasedField(String value) { @@ -181,87 +173,15 @@ public String toString() { } } - static final class Serializer extends JsonSerializer { - @Override - public void serialize(CamelCaseUnion value, JsonGenerator generator, SerializerProvider serializers) - throws IOException { - serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); - } - } - - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {CamelCasedField.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(CamelCaseUnion.class, VARIANT_TYPES); } @Override - public CamelCaseUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - CamelCaseUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - CamelCaseUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private CamelCaseUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - CamelCaseUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - CamelCaseUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(CamelCaseUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private CamelCaseUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected CamelCaseUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -269,50 +189,9 @@ private CamelCaseUnion deserializeSelected(JsonParser parser, DeserializationCon default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return (CamelCaseUnion) deserializer.deserialize(parser, context); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static CamelCaseUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - CamelCaseUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new Unknown(type, deserializeUnknown(parser, context)); } - return new Unknown(type, values); + return (CamelCaseUnion) deserializeVariant(parser, context, variantIndex); } } diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java index 51d823bfa..81240dbe5 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/EmptyUnion.java @@ -6,19 +6,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionSerializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -27,7 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -36,7 +28,7 @@ @Generated("com.palantir.conjure.java.types.UnionGenerator") @JsonDeserialize(using = EmptyUnion.Deserializer.class) -@JsonSerialize(using = EmptyUnion.Serializer.class) +@JsonSerialize(using = ConjureUnionSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class EmptyUnion permits EmptyUnion.Unknown { public static EmptyUnion unknown(@Safe String type, Object value) { @@ -111,136 +103,24 @@ public String toString() { } } - static final class Serializer extends JsonSerializer { - @Override - public void serialize(EmptyUnion value, JsonGenerator generator, SerializerProvider serializers) - throws IOException { - serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); - } - } - - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(EmptyUnion.class, VARIANT_TYPES); } @Override - public EmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private EmptyUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - EmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(EmptyUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected EmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return (EmptyUnion) deserializer.deserialize(parser, context); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static EmptyUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new Unknown(type, deserializeUnknown(parser, context)); } - return new Unknown(type, values); + return (EmptyUnion) deserializeVariant(parser, context, variantIndex); } } diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java index d4e9c9d92..d9e8efab7 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/NestedEmptyUnion.java @@ -8,19 +8,12 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionSerializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -29,7 +22,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -38,7 +30,7 @@ @Generated("com.palantir.conjure.java.types.UnionGenerator") @JsonDeserialize(using = NestedEmptyUnion.Deserializer.class) -@JsonSerialize(using = NestedEmptyUnion.Serializer.class) +@JsonSerialize(using = ConjureUnionSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class NestedEmptyUnion permits NestedEmptyUnion.Empty, NestedEmptyUnion.Unknown { public static NestedEmptyUnion empty(EmptyObject value) { @@ -181,87 +173,15 @@ public String toString() { } } - static final class Serializer extends JsonSerializer { - @Override - public void serialize(NestedEmptyUnion value, JsonGenerator generator, SerializerProvider serializers) - throws IOException { - serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); - } - } - - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Empty.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(NestedEmptyUnion.class, VARIANT_TYPES); } @Override - public NestedEmptyUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - NestedEmptyUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - NestedEmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private NestedEmptyUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - NestedEmptyUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - NestedEmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(NestedEmptyUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private NestedEmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected NestedEmptyUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -269,50 +189,9 @@ private NestedEmptyUnion deserializeSelected(JsonParser parser, DeserializationC default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return (NestedEmptyUnion) deserializer.deserialize(parser, context); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static NestedEmptyUnion deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - NestedEmptyUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new Unknown(type, deserializeUnknown(parser, context)); } - return new Unknown(type, values); + return (NestedEmptyUnion) deserializeVariant(parser, context, variantIndex); } } diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java index d121a7a4c..ca763d8db 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/SimpleUnion.java @@ -8,20 +8,13 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionSerializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -30,7 +23,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; @@ -40,7 +32,7 @@ @Generated("com.palantir.conjure.java.types.UnionGenerator") @JsonDeserialize(using = SimpleUnion.Deserializer.class) -@JsonSerialize(using = SimpleUnion.Serializer.class) +@JsonSerialize(using = ConjureUnionSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class SimpleUnion permits SimpleUnion.Foo, SimpleUnion.Bar, SimpleUnion.Baz, SimpleUnion.Unknown { @@ -295,87 +287,15 @@ public String toString() { } } - static final class Serializer extends JsonSerializer { - @Override - public void serialize(SimpleUnion value, JsonGenerator generator, SerializerProvider serializers) - throws IOException { - serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); - } - } - - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Foo.class, Bar.class, Baz.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(SimpleUnion.class, VARIANT_TYPES); } @Override - public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private SimpleUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -385,50 +305,9 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return (SimpleUnion) deserializer.deserialize(parser, context); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new Unknown(type, deserializeUnknown(parser, context)); } - return new Unknown(type, values); + return (SimpleUnion) deserializeVariant(parser, context, variantIndex); } } diff --git a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java index db41562af..c2845c375 100644 --- a/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java +++ b/conjure-java-core/src/integrationInput/java/sealedunions/com/palantir/product/UnionReservedNames.java @@ -8,19 +8,12 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; +import com.palantir.conjure.java.lib.internal.ConjureUnionSerializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -29,7 +22,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; @@ -38,7 +30,7 @@ @Generated("com.palantir.conjure.java.types.UnionGenerator") @JsonDeserialize(using = UnionReservedNames.Deserializer.class) -@JsonSerialize(using = UnionReservedNames.Serializer.class) +@JsonSerialize(using = ConjureUnionSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract sealed class UnionReservedNames permits UnionReservedNames.Known_, @@ -996,15 +988,7 @@ public String toString() { } } - static final class Serializer extends JsonSerializer { - @Override - public void serialize(UnionReservedNames value, JsonGenerator generator, SerializerProvider serializers) - throws IOException { - serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); - } - } - - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { Known_.class, Unknown_.class, @@ -1023,76 +1007,12 @@ static final class Deserializer extends JsonDeserializer { UnionReservedNames_.class }; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(UnionReservedNames.class, VARIANT_TYPES); } @Override - public UnionReservedNames deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - UnionReservedNames.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionReservedNames.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private UnionReservedNames deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionReservedNames.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionReservedNames.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(UnionReservedNames.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private UnionReservedNames deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected UnionReservedNames deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -1114,50 +1034,9 @@ private UnionReservedNames deserializeSelected(JsonParser parser, Deserializatio default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return (UnionReservedNames) deserializer.deserialize(parser, context); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static UnionReservedNames deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionReservedNames.class, "Expected the end of a JSON object while deserializing a union"); + return new Unknown(type, deserializeUnknown(parser, context)); } - return new Unknown(type, values); + return (UnionReservedNames) deserializeVariant(parser, context, variantIndex); } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java index f299a8815..151a29c85 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/EmptyUnionTypeExample.java @@ -7,14 +7,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -23,7 +18,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -189,130 +183,24 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(EmptyUnionTypeExample.class, VARIANT_TYPES); } @Override - public EmptyUnionTypeExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private EmptyUnionTypeExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(EmptyUnionTypeExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private EmptyUnionTypeExample deserializeSelected( + protected EmptyUnionTypeExample deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new EmptyUnionTypeExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static EmptyUnionTypeExample deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - EmptyUnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + return new EmptyUnionTypeExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new EmptyUnionTypeExample(new UnknownWrapper(type, values)); + return new EmptyUnionTypeExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java index 9ebe56d29..632992afe 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/ExternalLongUnionExample.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -26,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -270,82 +264,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {SafeLongWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(ExternalLongUnionExample.class, VARIANT_TYPES); } @Override - public ExternalLongUnionExample deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private ExternalLongUnionExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch( - ExternalLongUnionExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private ExternalLongUnionExample deserializeSelected( + protected ExternalLongUnionExample deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -353,51 +280,9 @@ private ExternalLongUnionExample deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new ExternalLongUnionExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static ExternalLongUnionExample deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - ExternalLongUnionExample.class, - "Expected the end of a JSON object while deserializing a union"); + return new ExternalLongUnionExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new ExternalLongUnionExample(new UnknownWrapper(type, values)); + return new ExternalLongUnionExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java index 4f1306e6a..1029c650c 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/LargeUnionExample.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; @@ -25,7 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.processing.Generated; @@ -5800,7 +5794,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { U0Wrapper.class, U1Wrapper.class, @@ -5905,76 +5899,12 @@ static final class Deserializer extends JsonDeserializer { U100Wrapper.class }; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(LargeUnionExample.class, VARIANT_TYPES); } @Override - public LargeUnionExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - LargeUnionExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - LargeUnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private LargeUnionExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - LargeUnionExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - LargeUnionExample.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(LargeUnionExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private LargeUnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected LargeUnionExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -6082,50 +6012,9 @@ private LargeUnionExample deserializeSelected(JsonParser parser, Deserialization default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new LargeUnionExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static LargeUnionExample deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - LargeUnionExample.class, "Expected the end of a JSON object while deserializing a union"); + return new LargeUnionExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new LargeUnionExample(new UnknownWrapper(type, values)); + return new LargeUnionExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java index e4e12b6d9..dcce6d1b2 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/SingleUnion.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -26,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -267,79 +261,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(SingleUnion.class, VARIANT_TYPES); } @Override - public SingleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - SingleUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SingleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private SingleUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SingleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SingleUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(SingleUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private SingleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected SingleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -347,50 +277,9 @@ private SingleUnion deserializeSelected(JsonParser parser, DeserializationContex default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new SingleUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static SingleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SingleUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new SingleUnion(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new SingleUnion(new UnknownWrapper(type, values)); + return new SingleUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java index eaa22fd29..59a1c0b3e 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/Union.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -26,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -440,78 +434,16 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(Union.class, VARIANT_TYPES); } @Override - public Union deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch(Union.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch(Union.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private Union deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - Union.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - Union.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(Union.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private Union deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected Union deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -521,50 +453,9 @@ private Union deserializeSelected(JsonParser parser, DeserializationContext cont default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new Union((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static Union deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - Union.class, "Expected the end of a JSON object while deserializing a union"); + return new Union(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new Union(new UnknownWrapper(type, values)); + return new Union((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java index 4afee0217..4a39b61ae 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionTypeExample.java @@ -11,14 +11,9 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -32,7 +27,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.DoubleFunction; import java.util.function.Function; import java.util.function.IntFunction; @@ -1659,7 +1653,7 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] { StringExampleWrapper.class, ThisFieldIsAnIntegerWrapper.class, @@ -1682,76 +1676,12 @@ static final class Deserializer extends JsonDeserializer { UnsafeDoubleWrapper.class }; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(UnionTypeExample.class, VARIANT_TYPES); } @Override - public UnionTypeExample deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - UnionTypeExample.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionTypeExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private UnionTypeExample deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionTypeExample.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(UnionTypeExample.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected UnionTypeExample deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -1777,50 +1707,9 @@ private UnionTypeExample deserializeSelected(JsonParser parser, DeserializationC default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new UnionTypeExample((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static UnionTypeExample deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionTypeExample.class, "Expected the end of a JSON object while deserializing a union"); + return new UnionTypeExample(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new UnionTypeExample(new UnknownWrapper(type, values)); + return new UnionTypeExample((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java index 66431a4cc..3b66568f8 100644 --- a/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java +++ b/conjure-java-core/src/integrationInput/java/template/com/palantir/product/UnionWithUnknownString.java @@ -10,14 +10,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -26,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -268,81 +262,15 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {Unknown_Wrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(UnionWithUnknownString.class, VARIANT_TYPES); } @Override - public UnionWithUnknownString deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - UnionWithUnknownString.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private UnionWithUnknownString deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - UnionWithUnknownString.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionWithUnknownString.class, - "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(UnionWithUnknownString.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private UnionWithUnknownString deserializeSelected( + protected UnionWithUnknownString deserializeSelected( JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -350,50 +278,9 @@ private UnionWithUnknownString deserializeSelected( default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new UnionWithUnknownString((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static UnionWithUnknownString deserializeUnknown( - JsonParser parser, DeserializationContext context, String type) throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - UnionWithUnknownString.class, "Expected the end of a JSON object while deserializing a union"); + return new UnionWithUnknownString(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new UnionWithUnknownString(new UnknownWrapper(type, values)); + return new UnionWithUnknownString((Base) deserializeVariant(parser, context, variantIndex)); } } } diff --git a/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java b/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java index ab9fb1aa9..c37c14abf 100644 --- a/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java +++ b/conjure-java-core/src/integrationInput/java/undertow/com/palantir/product/SimpleUnion.java @@ -10,15 +10,10 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.util.JsonParserSequence; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.util.TokenBuffer; import com.palantir.conjure.java.lib.SafeLong; +import com.palantir.conjure.java.lib.internal.ConjureUnionDeserializer; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; @@ -27,7 +22,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Function; import java.util.function.IntFunction; import javax.annotation.Nonnull; @@ -423,80 +417,16 @@ public String toString() { } } - static final class Deserializer extends JsonDeserializer { + static final class Deserializer extends ConjureUnionDeserializer { private static final Class[] VARIANT_TYPES = new Class[] {FooWrapper.class, BarWrapper.class, BazWrapper.class}; - private final AtomicReferenceArray> variantDeserializers = - new AtomicReferenceArray<>(VARIANT_TYPES.length); - - @Override - public boolean isCachable() { - return true; + Deserializer() { + super(SimpleUnion.class, VARIANT_TYPES); } @Override - public SimpleUnion deserialize(JsonParser parser, DeserializationContext context) throws IOException { - if (!parser.isExpectedStartObjectToken()) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected a JSON object for union deserialization"); - } - boolean acceptCaseInsensitiveProperties = - context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - JsonToken firstToken = parser.nextToken(); - if (firstToken == JsonToken.FIELD_NAME - && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { - if (parser.nextToken() != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - return deserializeSelected(parser, context, type); - } - return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); - } - - private SimpleUnion deserializeBuffered( - JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) - throws IOException { - try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { - buffer.writeStartObject(); - JsonToken token = parser.currentToken(); - while (token == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - JsonToken valueToken = parser.nextToken(); - if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { - if (valueToken != JsonToken.VALUE_STRING) { - return context.reportInputMismatch( - SimpleUnion.class, "Union discriminator 'type' must be a string"); - } - String type = parser.getText(); - parser.nextToken(); - try (JsonParser bufferedParser = buffer.asParser(parser)) { - JsonParser combinedParser = - JsonParserSequence.createFlattened(true, bufferedParser, parser); - combinedParser.nextToken(); - return deserializeSelected(combinedParser, context, type); - } - } - buffer.writeFieldName(fieldName); - buffer.copyCurrentStructure(parser); - token = parser.nextToken(); - } - if (token != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); - } - } - return context.reportInputMismatch(SimpleUnion.class, "Union discriminator 'type' is required"); - } - - private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { - return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); - } - - private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) + protected SimpleUnion deserializeSelected(JsonParser parser, DeserializationContext context, String type) throws IOException { int variantIndex = switch (type) { @@ -506,50 +436,9 @@ private SimpleUnion deserializeSelected(JsonParser parser, DeserializationContex default -> -1; }; if (variantIndex < 0) { - return deserializeUnknown(parser, context, type); - } - JsonDeserializer deserializer = variantDeserializers.get(variantIndex); - if (deserializer == null) { - deserializer = resolveDeserializer(context, variantIndex); - } - return new SimpleUnion((Base) deserializer.deserialize(parser, context)); - } - - private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) - throws JsonMappingException { - JsonDeserializer deserializer = - context.findContextualValueDeserializer(context.constructType(VARIANT_TYPES[variantIndex]), null); - if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { - return deserializer; - } - return variantDeserializers.get(variantIndex); - } - - private static SimpleUnion deserializeUnknown(JsonParser parser, DeserializationContext context, String type) - throws IOException { - Map values = new HashMap<>(); - JsonDeserializer valueDeserializer = null; - if (parser.currentToken() == JsonToken.START_OBJECT) { - parser.nextToken(); - } - while (parser.currentToken() == JsonToken.FIELD_NAME) { - String fieldName = parser.currentName(); - parser.nextToken(); - if (valueDeserializer == null) { - valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); - } - values.put( - fieldName, - parser.currentToken() == JsonToken.VALUE_NULL - ? valueDeserializer.getNullValue(context) - : valueDeserializer.deserialize(parser, context)); - parser.nextToken(); - } - if (parser.currentToken() != JsonToken.END_OBJECT) { - return context.reportInputMismatch( - SimpleUnion.class, "Expected the end of a JSON object while deserializing a union"); + return new SimpleUnion(new UnknownWrapper(type, deserializeUnknown(parser, context))); } - return new SimpleUnion(new UnknownWrapper(type, values)); + return new SimpleUnion((Base) deserializeVariant(parser, context, variantIndex)); } } } From c51cabb19240ccb17e4b1174be623d8b1921e22b Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Fri, 25 Sep 2026 14:38:32 +0000 Subject: [PATCH 9/9] Add shared union runtime helpers --- .../java/types/UnionGeneratorTests.java | 294 ++++++++++++++++++ .../lib/internal/ConjureMapDeserializer.java | 98 ++++++ .../internal/ConjureUnionDeserializer.java | 152 +++++++++ .../lib/internal/ConjureUnionSerializer.java | 30 ++ 4 files changed, 574 insertions(+) create mode 100644 conjure-java-core/src/test/java/com/palantir/conjure/java/types/UnionGeneratorTests.java create mode 100644 conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureMapDeserializer.java create mode 100644 conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureUnionDeserializer.java create mode 100644 conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureUnionSerializer.java diff --git a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/UnionGeneratorTests.java b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/UnionGeneratorTests.java new file mode 100644 index 000000000..fc8228c43 --- /dev/null +++ b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/UnionGeneratorTests.java @@ -0,0 +1,294 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.types; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.ValueInstantiator; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.palantir.conjure.defs.SafetyDeclarationRequirements; +import com.palantir.conjure.defs.validator.ConjureDefinitionValidator; +import com.palantir.conjure.java.Options; +import com.palantir.conjure.java.serialization.ObjectMappers; +import com.palantir.conjure.spec.AliasDefinition; +import com.palantir.conjure.spec.ConjureDefinition; +import com.palantir.conjure.spec.FieldDefinition; +import com.palantir.conjure.spec.FieldName; +import com.palantir.conjure.spec.MapType; +import com.palantir.conjure.spec.OptionalType; +import com.palantir.conjure.spec.PrimitiveType; +import com.palantir.conjure.spec.Type; +import com.palantir.conjure.spec.TypeDefinition; +import com.palantir.conjure.spec.TypeName; +import com.palantir.conjure.spec.UnionDefinition; +import com.palantir.javapoet.JavaFile; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +final class UnionGeneratorTests { + @TempDir + Path output; + + @ParameterizedTest + @CsvSource({"false,false", "true,false", "true,true"}) + void jacksonHelperNamesDoNotCollide(boolean sealed, boolean visitors) throws Exception { + ConjureDefinition.Builder definition = ConjureDefinition.builder().version(1); + for (String name : List.of("Serializer", "Deserializer", "Serializer_", "Deserializer_", "Named")) { + UnionDefinition.Builder union = UnionDefinition.builder().typeName(TypeName.of(name, "collision")); + for (String field : name.equals("Named") + ? List.of("serializer", "deserializer", "serializer_", "deserializer_") + : List.of("value")) { + union.union(FieldDefinition.builder() + .fieldName(FieldName.of(field)) + .type(Type.primitive(PrimitiveType.STRING)) + .build()); + } + definition.types(TypeDefinition.union(union.build())); + } + ConjureDefinition conjure = definition.build(); + ConjureDefinitionValidator.validateAll(conjure, SafetyDeclarationRequirements.ALLOWED); + List sources = new ObjectGenerator(Options.builder() + .sealedUnions(sealed) + .sealedUnionVisitors(visitors) + .build()) + .generate(conjure) + .map(JavaFile::toJavaFileObject) + .toList(); + compile(sources); + try (URLClassLoader loader = new URLClassLoader( + new URL[] {output.toUri().toURL()}, getClass().getClassLoader())) { + ObjectMapper mapper = new ObjectMapper(); + for (String name : List.of("Serializer", "Deserializer", "Serializer_", "Deserializer_", "Named")) { + Class type = loader.loadClass("collision." + name); + for (String field : name.equals("Named") + ? List.of("serializer", "deserializer", "serializer_", "deserializer_") + : List.of("value")) { + String json = "{\"type\":\"" + field + "\",\"" + field + "\":\"test\"}"; + Object value = type.getMethod(field, String.class).invoke(null, "test"); + assertThat(mapper.writerFor(type).writeValueAsString(value)).isEqualTo(json); + assertThat(mapper.readValue(json, type)).isEqualTo(value); + if (sealed) { + assertThat(value.getClass().getSimpleName()) + .isEqualTo(Character.toUpperCase(field.charAt(0)) + field.substring(1)); + } + } + } + } + } + + @ParameterizedTest + @CsvSource({ + "false,false,false", "false,false,true", "false,true,false", "false,true,true", + "true,false,false", "true,false,true", "true,true,false", "true,true,true" + }) + void mapOwnershipAndWireSemantics(boolean sealed, boolean defensive, boolean nonNull) throws Exception { + Type string = Type.primitive(PrimitiveType.STRING); + Type map = Type.map(MapType.of(string, string)); + TypeName alias = TypeName.of("MapAlias", "ownership"); + ConjureDefinition definition = ConjureDefinition.builder() + .version(1) + .types(TypeDefinition.alias( + AliasDefinition.builder().typeName(alias).alias(map).build())) + .types(TypeDefinition.union(UnionDefinition.builder() + .typeName(TypeName.of("MapUnion", "ownership")) + .union(FieldDefinition.builder() + .fieldName(FieldName.of("fromJson")) + .type(map) + .build()) + .union(FieldDefinition.builder() + .fieldName(FieldName.of("fromJson_")) + .type(map) + .build()) + .union(FieldDefinition.builder() + .fieldName(FieldName.of("map")) + .type(map) + .build()) + .union(FieldDefinition.builder() + .fieldName(FieldName.of("mapOptional")) + .type(Type.map(MapType.of(string, Type.optional(OptionalType.of(string))))) + .build()) + .union(FieldDefinition.builder() + .fieldName(FieldName.of("alias")) + .type(Type.reference(alias)) + .build()) + .build())) + .build(); + compile(new ObjectGenerator(Options.builder() + .sealedUnions(sealed) + .defensiveCollections(defensive) + .nonNullCollections(nonNull) + .build()) + .generate(definition) + .map(JavaFile::toJavaFileObject) + .toList()); + try (URLClassLoader loader = new URLClassLoader( + new URL[] {output.toUri().toURL()}, getClass().getClassLoader())) { + Class type = loader.loadClass("ownership.MapUnion"); + ObjectMapper mapper = ObjectMappers.newServerObjectMapper(); + for (String json : List.of( + "{\"type\":\"map\",\"map\":{\"z\":\"last\",\"a\":\"first\"}}", + "{\"map\":{\"z\":\"last\",\"a\":\"first\"},\"type\":\"map\"}")) { + Object union = mapper.readValue(json, type); + Map value = mapValue(union, sealed); + assertThat(new ArrayList<>(value.keySet())).isEqualTo(List.of("z", "a")); + assertThat(mapper.readTree(mapper.writeValueAsString(union))).isEqualTo(mapper.readTree(json)); + if (defensive) { + assertThatThrownBy(value::clear).isInstanceOf(UnsupportedOperationException.class); + } + } + for (String field : List.of("map", "mapOptional", "alias", "fromJson", "fromJson_")) { + for (String json : List.of( + "{\"type\":\"" + field + "\"}", "{\"type\":\"" + field + "\",\"" + field + "\":null}")) { + Object union = mapper.readValue(json, type); + assertThat(mapper.readTree(mapper.writeValueAsBytes(union)) + .get(field) + .isEmpty()) + .isTrue(); + } + } + Object optional = mapper.readValue("{\"type\":\"mapOptional\",\"mapOptional\":{\"empty\":null}}", type); + assertThat(mapper.readTree(mapper.writeValueAsBytes(optional)) + .get("mapOptional") + .get("empty") + .isNull()) + .isTrue(); + String nullElement = "{\"type\":\"map\",\"map\":{\"empty\":null}}"; + if (defensive && nonNull) { + assertThatThrownBy(() -> mapper.readValue(nullElement, type)).isInstanceOf(IOException.class); + } else { + assertThat(mapper.readTree(mapper.writeValueAsBytes(mapper.readValue(nullElement, type)))) + .isEqualTo(mapper.readTree(nullElement)); + } + Map callerMap = new LinkedHashMap<>(Map.of("original", "value")); + Object publicValue = type.getMethod("map", Map.class).invoke(null, callerMap); + callerMap.put("added", "value"); + assertThat(mapValue(publicValue, sealed).containsKey("added")).isEqualTo(!defensive); + + if (defensive) { + Map shared = new LinkedHashMap<>(Map.of("shared", "original")); + ObjectMapper customMapper = ObjectMappers.newServerObjectMapper() + .registerModule(new SimpleModule().addDeserializer(Map.class, new JsonDeserializer<>() { + @Override + public Map deserialize(JsonParser parser, DeserializationContext _context) + throws IOException { + parser.skipChildren(); + return shared; + } + + @Override + public Map getEmptyValue(DeserializationContext _context) { + return shared; + } + })); + ObjectMapper customConstructorMapper = ObjectMappers.newServerObjectMapper() + .registerModule(new SimpleModule() + .addValueInstantiator( + LinkedHashMap.class, new ValueInstantiator.Base(LinkedHashMap.class) { + @Override + public boolean canCreateUsingDefault() { + return true; + } + + @Override + public Object createUsingDefault(DeserializationContext _context) { + return shared; + } + })); + for (ObjectMapper custom : List.of(customMapper, customConstructorMapper)) { + for (String json : List.of( + "{\"type\":\"map\",\"map\":{}}", "{\"type\":\"map\",\"map\":null}", "{\"type\":\"map\"}")) { + shared.put("shared", "original"); + Object union = custom.readValue(json, type); + shared.put("shared", "modified"); + assertThat(mapValue(union, sealed).get("shared")).isEqualTo("original"); + assertThatThrownBy(mapValue(union, sealed)::clear) + .isInstanceOf(UnsupportedOperationException.class); + } + } + } + } + } + + private static Map mapValue(Object union, boolean sealed) throws Exception { + Object wrapper = union; + if (!sealed) { + Method getValue = union.getClass().getDeclaredMethod("getValue"); + getValue.setAccessible(true); + wrapper = getValue.invoke(union); + } + Method accessor = wrapper.getClass().getDeclaredMethod(sealed ? "value" : "getValue"); + accessor.setAccessible(true); + return (Map) accessor.invoke(wrapper); + } + + private void compile(List sources) throws IOException { + List classpath = new ArrayList<>(); + classpath.add(System.getProperty("java.class.path")); + for (ClassLoader loader = getClass().getClassLoader(); loader != null; loader = loader.getParent()) { + if (loader instanceof URLClassLoader urlLoader) { + Arrays.stream(urlLoader.getURLs()).map(URL::getPath).forEach(classpath::add); + } + } + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + try (StandardJavaFileManager fileManager = + compiler.getStandardFileManager(diagnostics, Locale.ROOT, StandardCharsets.UTF_8)) { + boolean succeeded = compiler.getTask( + null, + fileManager, + diagnostics, + List.of( + "--release", + "17", + "-proc:none", + "-d", + output.toString(), + "-classpath", + String.join(File.pathSeparator, classpath)), + null, + sources) + .call(); + assertThat(succeeded) + .describedAs("Generated sources must compile: %s", diagnostics.getDiagnostics()) + .isTrue(); + } + } +} diff --git a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureMapDeserializer.java b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureMapDeserializer.java new file mode 100644 index 000000000..1d321fd06 --- /dev/null +++ b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureMapDeserializer.java @@ -0,0 +1,98 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.lib.internal; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators; +import com.fasterxml.jackson.databind.deser.std.DelegatingDeserializer; +import com.fasterxml.jackson.databind.deser.std.MapDeserializer; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.palantir.logsafe.exceptions.SafeIllegalStateException; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +/** Internal Jackson support for transferring ownership of maps to generated union values. */ +public final class ConjureMapDeserializer extends JsonDeserializer implements ContextualDeserializer { + @Override + public JsonDeserializer createContextual(DeserializationContext context, BeanProperty property) + throws JsonMappingException { + JsonDeserializer delegate = context.findContextualValueDeserializer(property.getType(), property); + // Only Jackson's standard LinkedHashMap constructor guarantees a fresh, privately owned map. + if (delegate.getClass() == MapDeserializer.class + && ((MapDeserializer) delegate).getValueInstantiator().getClass() + == JDKValueInstantiators.findStdValueInstantiator(context.getConfig(), LinkedHashMap.class) + .getClass() + && property.getType().getTypeHandler() == null + && context.getConfig().findTypeDeserializer(property.getType()) == null) { + return delegate; + } + return new CopyingDeserializer(delegate); + } + + @Override + public Object deserialize(JsonParser _parser, DeserializationContext _context) { + throw new SafeIllegalStateException("Map deserializer must be contextualized"); + } + + private static final class CopyingDeserializer extends DelegatingDeserializer { + CopyingDeserializer(JsonDeserializer delegate) { + super(delegate); + } + + @Override + protected JsonDeserializer newDelegatingInstance(JsonDeserializer delegate) { + return new CopyingDeserializer(delegate); + } + + @Override + public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException { + return copy(super.deserialize(parser, context)); + } + + @Override + public Object deserializeWithType( + JsonParser parser, DeserializationContext context, TypeDeserializer typeDeserializer) + throws IOException { + return copy(super.deserializeWithType(parser, context, typeDeserializer)); + } + + @Override + public Object getNullValue(DeserializationContext context) throws JsonMappingException { + return copy(super.getNullValue(context)); + } + + @Override + public Object getEmptyValue(DeserializationContext context) throws JsonMappingException { + return copy(super.getEmptyValue(context)); + } + + @Override + public Object getAbsentValue(DeserializationContext context) throws JsonMappingException { + return copy(super.getAbsentValue(context)); + } + + private static Object copy(Object value) { + return value == null ? null : new LinkedHashMap<>((Map) value); + } + } +} diff --git a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureUnionDeserializer.java b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureUnionDeserializer.java new file mode 100644 index 000000000..f7c678858 --- /dev/null +++ b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureUnionDeserializer.java @@ -0,0 +1,152 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.lib.internal; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserSequence; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.util.TokenBuffer; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReferenceArray; + +/** Internal Jackson support shared by generated union deserializers. */ +public abstract class ConjureUnionDeserializer extends JsonDeserializer { + private final Class unionClass; + private final Class[] variantTypes; + private final AtomicReferenceArray> variantDeserializers; + + protected ConjureUnionDeserializer(Class unionClass, Class[] variantTypes) { + this.unionClass = unionClass; + this.variantTypes = variantTypes; + this.variantDeserializers = new AtomicReferenceArray<>(variantTypes.length); + } + + @Override + public final boolean isCachable() { + return true; + } + + @Override + public final T deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (!parser.isExpectedStartObjectToken()) { + return context.reportInputMismatch(unionClass, "Expected a JSON object for union deserialization"); + } + boolean acceptCaseInsensitiveProperties = context.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); + JsonToken firstToken = parser.nextToken(); + if (firstToken == JsonToken.FIELD_NAME && isTypeField(parser.currentName(), acceptCaseInsensitiveProperties)) { + if (parser.nextToken() != JsonToken.VALUE_STRING) { + return context.reportInputMismatch(unionClass, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + return deserializeSelected(parser, context, type); + } + return deserializeBuffered(parser, context, acceptCaseInsensitiveProperties); + } + + private T deserializeBuffered( + JsonParser parser, DeserializationContext context, boolean acceptCaseInsensitiveProperties) + throws IOException { + try (TokenBuffer buffer = context.bufferForInputBuffering(parser)) { + buffer.writeStartObject(); + JsonToken token = parser.currentToken(); + while (token == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + JsonToken valueToken = parser.nextToken(); + if (isTypeField(fieldName, acceptCaseInsensitiveProperties)) { + if (valueToken != JsonToken.VALUE_STRING) { + return context.reportInputMismatch(unionClass, "Union discriminator 'type' must be a string"); + } + String type = parser.getText(); + parser.nextToken(); + try (JsonParser bufferedParser = buffer.asParser(parser)) { + JsonParser combinedParser = JsonParserSequence.createFlattened(true, bufferedParser, parser); + combinedParser.nextToken(); + return deserializeSelected(combinedParser, context, type); + } + } + buffer.writeFieldName(fieldName); + buffer.copyCurrentStructure(parser); + token = parser.nextToken(); + } + if (token != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + unionClass, "Expected the end of a JSON object while deserializing a union"); + } + } + return context.reportInputMismatch(unionClass, "Union discriminator 'type' is required"); + } + + private static boolean isTypeField(String fieldName, boolean acceptCaseInsensitiveProperties) { + return "type".equals(fieldName) || (acceptCaseInsensitiveProperties && "type".equalsIgnoreCase(fieldName)); + } + + protected abstract T deserializeSelected(JsonParser parser, DeserializationContext context, String type) + throws IOException; + + protected final Object deserializeVariant(JsonParser parser, DeserializationContext context, int variantIndex) + throws IOException { + JsonDeserializer deserializer = variantDeserializers.get(variantIndex); + if (deserializer == null) { + deserializer = resolveDeserializer(context, variantIndex); + } + return deserializer.deserialize(parser, context); + } + + private JsonDeserializer resolveDeserializer(DeserializationContext context, int variantIndex) + throws JsonMappingException { + JsonDeserializer deserializer = + context.findContextualValueDeserializer(context.constructType(variantTypes[variantIndex]), null); + if (variantDeserializers.compareAndSet(variantIndex, null, deserializer)) { + return deserializer; + } + return variantDeserializers.get(variantIndex); + } + + protected final Map deserializeUnknown(JsonParser parser, DeserializationContext context) + throws IOException { + Map values = new HashMap<>(); + JsonDeserializer valueDeserializer = null; + if (parser.currentToken() == JsonToken.START_OBJECT) { + parser.nextToken(); + } + while (parser.currentToken() == JsonToken.FIELD_NAME) { + String fieldName = parser.currentName(); + parser.nextToken(); + if (valueDeserializer == null) { + valueDeserializer = context.findRootValueDeserializer(context.constructType(Object.class)); + } + values.put( + fieldName, + parser.currentToken() == JsonToken.VALUE_NULL + ? valueDeserializer.getNullValue(context) + : valueDeserializer.deserialize(parser, context)); + parser.nextToken(); + } + if (parser.currentToken() != JsonToken.END_OBJECT) { + return context.reportInputMismatch( + unionClass, "Expected the end of a JSON object while deserializing a union"); + } + return values; + } +} diff --git a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureUnionSerializer.java b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureUnionSerializer.java new file mode 100644 index 000000000..e591c4a4f --- /dev/null +++ b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureUnionSerializer.java @@ -0,0 +1,30 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.lib.internal; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; + +/** Internal Jackson serializer that delegates from a sealed union to its concrete variant. */ +public final class ConjureUnionSerializer extends JsonSerializer { + @Override + public void serialize(Object value, JsonGenerator generator, SerializerProvider serializers) throws IOException { + serializers.findValueSerializer(value.getClass()).serialize(value, generator, serializers); + } +}