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

    to-evaluateIssue that has been received but not yet evaluated

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions