Skip to content

Fix nested subtypes tagged union generation (#1005)#1127

Open
almarzn wants to merge 1 commit intovojtechhabarta:mainfrom
almarzn:fix/nested-subtypes-tagged-union
Open

Fix nested subtypes tagged union generation (#1005)#1127
almarzn wants to merge 1 commit intovojtechhabarta:mainfrom
almarzn:fix/nested-subtypes-tagged-union

Conversation

@almarzn
Copy link
Copy Markdown

@almarzn almarzn commented Apr 19, 2026

Summary

Fixes #1005 - Incorrect tagged unions when using Jackson and nested subtypes.

Problem

When using Jackson's @JsonTypeInfo and @JsonSubTypes with nested subtypes (intermediate abstract classes that also have @JsonSubTypes), the generated tagged union types referenced the intermediate interfaces directly instead of their own tagged union types.

For example, with this hierarchy:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({@Type(Mammal.class), @Type(Bird.class)})
abstract class Animal {}

@JsonSubTypes({@Type(Cat.class), @Type(Dog.class)})
abstract class Mammal extends Animal {}

@JsonSubTypes({@Type(Parrot.class), @Type(Duck.class)})
abstract class Bird extends Animal {}

Before (buggy):

type AnimalUnion = Mammal | Bird;
type MammalUnion = Cat | Dog;
type BirdUnion = Parrot | Duck;

This meant const animal: AnimalUnion = { type: "Cat", ... } would fail with a TypeScript error because Cat is not assignable to Mammal.

After (fixed):

type AnimalUnion = MammalUnion | BirdUnion;
type MammalUnion = Cat | Dog;
type BirdUnion = Parrot | Duck;

Solution

Modified ModelCompiler.createAndUseTaggedUnions to:

  1. Build a map of Class<?> -> Symbol for all beans that have tagged unions before constructing the unions
  2. When building union types, check if each child class has its own union alias (via the map)
  3. Only replace with the union alias when the child appears as a named entry in the parent's @JsonSubTypes annotation (ensuring backward compatibility with existing behavior for intermediate types without names)

Testing

  • Added new test TaggedUnionsTest.testNestedSubtypesTaggedUnion that reproduces the issue
  • All 378 tests in the core module pass
  • Backward compatibility verified with existing testIntermediateUnions test

When using Jackson's @JsonTypeInfo and @JsonSubTypes with nested subtypes
(intermediate abstract classes that also have @JsonSubTypes), the generated
tagged union types now correctly reference the intermediate subtypes'
union aliases instead of the intermediate interfaces directly.

For example, with hierarchy:
  Animal -> Mammal -> Cat/Dog
         -> Bird   -> Parrot/Duck

Before:
  type AnimalUnion = Mammal | Bird;
  type MammalUnion = Cat | Dog;
  type BirdUnion = Parrot | Duck;

After:
  type AnimalUnion = MammalUnion | BirdUnion;
  type MammalUnion = Cat | Dog;
  type BirdUnion = Parrot | Duck;

The fix builds a map of class -> union symbol before constructing the
unions, allowing nested subtypes to be replaced by their union aliases
when they appear as named entries in their parent's @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.

Incorrect tagged unions when using Jackson and nested subtypes

1 participant