Skip to content

Conversation

YoungHoney
Copy link

@YoungHoney YoungHoney commented Aug 26, 2025

Motivation

This pull request implements support for Jackson's polymorphism annotations (@JsonTypeInfo, @JsonSubTypes) in DocService, as requested in the community (issue #6313). Currently, DocService does not correctly generate documentation for annotated services that use inheritance in their DTOs, leading to incomplete specifications. This change adds a new DescriptiveTypeInfoProvider to resolve these polymorphic types and generate accurate JSON Schemas.

However, this feature has uncovered significant and complex build stability issues when running a full parallel build (./gradlew clean build --parallel). This PR serves as both the implementation of the feature and a concrete test case for discussing the build instability it triggers.

Modifications

  • Added JacksonPolymorphismTypeInfoProvider: A new provider that uses pure Java reflection to safely inspect @JsonTypeInfo and @JsonSubTypes annotations. It is registered via Java's SPI mechanism to be discoverable by DocService.
  • Added DiscriminatorInfo: A new data class to hold polymorphism metadata extracted from the annotations.
  • Consolidated Type Utilities: General-purpose type conversion logic (e.g., toTypeSignature) was moved from a separate DocServiceTypeUtil into AnnotatedDocServicePlugin for better cohesion.
  • Updated StructInfo: Modified to include oneOf and discriminator fields to carry polymorphism information.
  • Updated JsonSchemaGenerator: The generator now recognizes the new fields in StructInfo and correctly produces JSON Schema with oneOf and discriminator properties.
  • Added PolymorphismDocServiceExample: A new example service to demonstrate and manually verify the feature.

Result

  • DocService can now correctly generate documentation for annotated services that use polymorphic types with Jackson. The resulting JSON Schema will contain the appropriate oneOf and discriminator fields.
  • Known Issue: This change is known to trigger build instability in the project's CI environment. A detailed summary of the investigation is provided here : Request Guidance on Build Issues in my feature branch #6369

Example usage

also, you can try this at PolymorphismDocServiceExample

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "species")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
interface Animal {
    // ...
}
"structs" : [ {
    "name" : "example.armeria.server.animal.PolymorphismDocServiceExample$Animal",
    "fields" : [ ],
    "descriptionInfo" : {
      "docString" : "",
      "markup" : "NONE"
    },
    "oneOf" : [ "example.armeria.server.animal.PolymorphismDocServiceExample$Dog", "example.armeria.server.animal.PolymorphismDocServiceExample$Cat" ],
    "discriminator" : {
      "propertyName" : "species",
      "mapping" : {
        "dog" : "#/definitions/example.armeria.server.animal.PolymorphismDocServiceExample$Dog",
        "cat" : "#/definitions/example.armeria.server.animal.PolymorphismDocServiceExample$Cat"
      }
    }

@CLAassistant
Copy link

CLAassistant commented Aug 26, 2025

CLA assistant check
All committers have signed the CLA.

@YoungHoney YoungHoney marked this pull request as ready for review August 26, 2025 13:35
@YoungHoney YoungHoney marked this pull request as draft August 26, 2025 13:35
@minwoox
Copy link
Contributor

minwoox commented Aug 28, 2025

However, this feature has uncovered significant and complex build stability issues when running a full parallel build (./gradlew clean build --parallel).

I will investigate it.
It seems like your changes aren't related to the failure, so please feel free to change the draft status when you are ready.

YoungHoney added a commit to YoungHoney/armeria that referenced this pull request Sep 1, 2025
Adds a new `JacksonPolymorphismTypeInfoProvider` to generate correct
JSON Schemas with `oneOf` and `discriminator` for polymorphic types
annotated with `@JsonTypeInfo` and `@JsonSubTypes`.
@YoungHoney YoungHoney marked this pull request as ready for review September 1, 2025 13:18
Adds a new `JacksonPolymorphismTypeInfoProvider` to generate correct
JSON Schemas with `oneOf` and `discriminator` for polymorphic types
annotated with `@JsonTypeInfo` and `@JsonSubTypes`.
Copy link

codecov bot commented Sep 3, 2025

Codecov Report

❌ Patch coverage is 71.04558% with 108 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.05%. Comparing base (8150425) to head (e7a0237).
⚠️ Report is 175 commits behind head on main.

Files with missing lines Patch % Lines
...a/server/animal/PolymorphismDocServiceExample.java 0.00% 42 Missing ⚠️
...ecorp/armeria/server/docs/JsonSchemaGenerator.java 83.69% 13 Missing and 17 partials ⚠️
...necorp/armeria/server/docs/DocServiceTypeUtil.java 84.21% 6 Missing and 6 partials ⚠️
...inecorp/armeria/server/docs/DiscriminatorInfo.java 40.00% 9 Missing ⚠️
...a/com/linecorp/armeria/server/docs/StructInfo.java 57.89% 5 Missing and 3 partials ⚠️
...rver/docs/JacksonPolymorphismTypeInfoProvider.java 81.08% 3 Missing and 4 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6370      +/-   ##
============================================
- Coverage     74.46%   74.05%   -0.42%     
- Complexity    22234    23003     +769     
============================================
  Files          1963     2064     +101     
  Lines         82437    86224    +3787     
  Branches      10764    11332     +568     
============================================
+ Hits          61385    63851    +2466     
- Misses        15918    16938    +1020     
- Partials       5134     5435     +301     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

logger.info("JSON Specification: http://127.0.0.1:8080/docs/specification.json");
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "species")
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like the subtypes are missing species, so deserialization actually doesn't work.
Also, this is not a tutorial, but just an example.
So, what do you think of making this class a test case (PolymorphismDocServiceTest) like we did for AnnotatedDocServiceTest?
We can use TestUtil.isDocServiceDemoMode() to see how it works on a browser.

Could you also use English for comments instead of Korean?

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Left a few more suggestions. 😎
Please, keep up the great work. 👍

}

final Map<String, String> mapping = new LinkedHashMap<>();
Arrays.stream(jsonSubTypes.value()).forEach(subType -> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also handle the case where jsonSubTypes.value() is empty?

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for question regarding the empty subTypes case.

Here is a summary of my findings.

Test Setup

I created a misconfigured DTO specifically for this test:

// DTO with an intentionally empty @JsonSubTypes annotation
@JsonTypeInfo(use = JsonTypeTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({})
interface MisconfiguredAnimal {
    String name();
}

1. Current Code

With the current implementation, when DocService processes MisconfiguredAnimal, it generates the following StructInfo in specification.json:

  • The oneOf field is missing. This is because the list is empty and the field is annotated with @JsonInclude(Include.NON_EMPTY).
  • The discriminator field is present, but its mapping is an empty object.

Resulting StructInfo snippet:

 "name" : "...MisconfiguredAnimal",
    "fields" : [ ],
    "descriptionInfo" : {
      "docString" : "",
      "markup" : "NONE"
    },
    "discriminator" : {
      "propertyName" : "type",
      "mapping" : { }
    }

This result seems inconsistent, as it describes a discriminator without any subtypes in oneOf.

2. After adding return null

After adding defensive check to JacksonPolymorphismTypeInfoProvider:

if (jsonSubTypes.value().length == 0) {
    return null;
}

final Map<String, String> mapping = new LinkedHashMap<>();
...

The provider returns null for MisconfiguredAnimal. The request then falls back to DefaultDescriptiveTypeInfoProvider, which correctly treats it as a generic interface.

Resulting StructInfo snippet:

{
    "name" : "...MisconfiguredAnimal",
    "fields" : [ ],
    "descriptionInfo" : {
      "docString" : "",
      "markup" : "NONE"
    }
  }

This result is clean and does not contain any misleading or incomplete polymorphism information.

Conclusion & Question

i think second one looks better, can i do this?

Thank you.

Copy link
Contributor

Choose a reason for hiding this comment

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

i think second one looks better, can i do this?

Yeah, that looks good. 👍

if (structInfo != null) {
visited.put(firstParam.typeSignature(), "#");
generateProperties(structInfo.fields(), visited, "#", root);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we warn if structInfo == null?

fieldNode.set("additionalProperties", additionalPropertiesNode.get(""));
}
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What happends if the type is other types such as CONTAINER?

YoungHoney added a commit to YoungHoney/armeria that referenced this pull request Sep 7, 2025
Adds a new `JacksonPolymorphismTypeInfoProvider` to generate correct
JSON Schemas with `oneOf` and `discriminator` for polymorphic types
annotated with `@JsonTypeInfo` and `@JsonSubTypes`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants