Skip to content

Custom JsonTypeIdResolver is not invoked when object is part of generic type #5625

@nstdio

Description

@nstdio

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

Please see the reproducible

Version Information

3.0.4

Reproduction

The JacksonTest#nestedMap and JacksonTest#nestedGenericRecord are failing.

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import java.util.Map;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DatabindContext;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.annotation.JsonTypeIdResolver;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.impl.TypeIdResolverBase;

public class JacksonTest {

  private final JsonMapper mapper = JsonMapper.builder()
      .enable(SerializationFeature.INDENT_OUTPUT)
      .build();

  @Test
  void root() {
    //given
    var bar = new Bar("test");
    var expected = """
        {
          "@type" : "BAR",
          "any" : "test"
        }""";

    //when
    var actual = mapper.writeValueAsString(bar);

    //then
    assertEquals(expected, actual);
  }

  @Test
  void nestedMap() {
    //given
    var map = Map.of("bar", new Bar("test"));
    var expected = """
        {
          "bar" : {
            "@type" : "BAR",
            "any" : "test"
          }
        }""";

    //when
    var actual = mapper.writeValueAsString(map);

    //then
    assertEquals(expected, actual);
  }

  @Test
  void nestedRecord() {
    //given
    record Box(Foo value) {

    }
    var box = new Box(new Qux(1));
    var expected = """
        {
          "value" : {
            "@type" : "QUX",
            "any" : 1
          }
        }""";

    //when
    var actual = mapper.writeValueAsString(box);

    //then
    assertEquals(expected, actual);
  }

  @Test
  void nestedGenericRecord() {
    //given
    record Box<T>(T value) {

    }
    var box = new Box<>(new Qux(1));
    var expected = """
        {
          "value" : {
            "@type" : "QUX",
            "any" : 1
          }
        }""";

    //when
    var actual = mapper.writeValueAsString(box);

    //then
    assertEquals(expected, actual);
  }

  @Test
  void nestedGenericRecordExplicitWriter() {
    //given
    record Box<T>(T value) {

    }
    var box = new Box<>(new Qux(1));
    var expected = """
        {
          "value" : {
            "@type" : "QUX",
            "any" : 1
          }
        }""";

    var writer = mapper.writer().forType(new TypeReference<Box<Foo>>() {
    });

    //when
    var actual = writer.writeValueAsString(box);

    //then
    assertEquals(expected, actual);
  }

  @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "@type")
  @JsonTypeIdResolver(FooIdResolver.class)
  public sealed interface Foo permits Bar, Qux {

  }

  record Bar(String any) implements Foo {

  }

  record Qux(int any) implements Foo {

  }

  public static class FooIdResolver extends TypeIdResolverBase {

    @Override
    public String idFromValue(DatabindContext ctxt, Object value) {
      return idFromValueAndType(ctxt, value, value.getClass());
    }

    @Override
    public String idFromValueAndType(DatabindContext ctxt, Object value, Class<?> suggestedType) {
      return switch (value) {
        case Bar _ -> "BAR";
        case Qux _ -> "QUX";
        default -> throw new IllegalStateException("Unexpected value: " + value);
      };
    }

    @Override
    public Id getMechanism() {
      return Id.CUSTOM;
    }
  }
}

Expected behavior

No response

Additional context

No response

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions