Skip to content

Generics using wildcard not correctly resolved #5285

@wimdeblauwe

Description

@wimdeblauwe

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

I opened a Spring Boot issue, but it seems the issue is Jackson related. See spring-projects/spring-boot#46994 (comment) for the full details and a reproducer.

Version Information

2.19.2

Reproduction

package com.example.jsontesterbug;

import static org.assertj.core.api.Assertions.assertThat;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

class MessageWrapperTest {
	
  private MessageWrapper<?> wildcardWrapper;
  
  private MessageWrapper<EmailSettings> specificWrapper;

  private ObjectMapper objectMapper = new ObjectMapper();
  
  @Test
  void wildcardWrapper() throws NoSuchFieldException, SecurityException, JsonProcessingException {
	  serializeWithTypeFromField("wildcardWrapper");
  }
  
  @Test
  void specificWrapper() throws NoSuchFieldException, SecurityException, JsonProcessingException {
	  serializeWithTypeFromField("specificWrapper");
  }
  
  private void serializeWithTypeFromField(String field) throws NoSuchFieldException, SecurityException, JsonProcessingException {
	  MessageWrapper<EmailSettings> wrapper = new MessageWrapper<>(new EmailSettings("[email protected]"),
			  "Sample Message");
	  Type genericType = MessageWrapperTest.class.getDeclaredField(field).getGenericType();
	  TypeVariable<?>[] typeParameters = ((Class<?>)((ParameterizedType)genericType).getRawType()).getTypeParameters();
	  Type[] bounds = typeParameters[0].getBounds();
	  System.out.println(field);
	  System.out.println("  Generic type: " + genericType);
	  System.out.println("    Bounds: " + Arrays.toString(bounds));
	  JavaType jacksonType = this.objectMapper.constructType(genericType);
	  System.out.println("  Jackson type: " + jacksonType);
	  String json = this.objectMapper.writerFor(jacksonType).writeValueAsString(wrapper);
	  System.out.println("  JSON: " + json);
	  assertThat(json).contains("\"type\":\"EMAIL\"");
	  assertThat(json).contains("\"email\":\"[email protected]\"");
  }

}

Running this gives this output:

wildcardWrapper
  Generic type: com.example.jsontesterbug.MessageWrapper<?>
    Bounds: [interface com.example.jsontesterbug.Settings]
  Jackson type: [simple type, class com.example.jsontesterbug.MessageWrapper<java.lang.Object>]
  JSON: {"settings":{"email":"[email protected]"},"message":"Sample Message"}
specificWrapper
  Generic type: com.example.jsontesterbug.MessageWrapper<com.example.jsontesterbug.EmailSettings>
    Bounds: [interface com.example.jsontesterbug.Settings]
  Jackson type: [simple type, class com.example.jsontesterbug.MessageWrapper<com.example.jsontesterbug.EmailSettings>]
  JSON: {"settings":{"type":"EMAIL","email":"[email protected]"},"message":"Sample Message"}

Expected behavior

The JSON output should be the same in both cases.

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.19Issues planned at 2.19 or later

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions