2

Jackson's TypeReference allows reifying the generic type parameters of a type via subclassing. Example:

final var typeRef = new TypeReference<List<MyType<MyArg>>>() {}; final List<MyType<MyArg>> list = mapper.readValue(input, typeRef); final MyType<MyArg> first = list.get(0); 

This only works if the full type is known at time of sub classing. Extracting the conversion call in a generic method will not work:

<T> T convert(final String input) { final var typeRef = new TypeReference<List<MyType<T>>>() {}; final List<MyType<T>> list = mapper.readValue(input, typeRef); return list; } final MyType<MyArg> first = list.get(0); 

(because it erases to new TypeReference<List<MyType<Object>>>() {}, which will likely be deserialized to List<MyType<Map<String, Object>>>).


I want to deserialize and unwrap instances of generic types, without providing the full type signature at the call site. The call site should only be concerned about the inner (wrapped) type, since that's the only type it will interact with. Given the following record definitions:

private record MyResponse<T>(MyResult<T> result) {} private record MyResult<T>(List<T> values) {} private record MyStringValue(String name) { @Override public String toString() { return "'" + name + "'"; } } 

and a method

<T> MyResult<T> convert(final String input, final TypeReference<MyResponse<T>> typeRef) { try { return objectMapper.readValue(input, typeRef).result(); } catch (final JsonProcessingException ex) { throw new RuntimeException(ex); } } 

how can I unwrap the result of this function and only return an instance of T, without providing the full type ref TypeReference<MyResponse<MyResult<T>>>?

I have:

<T> List<T> unwrap(final String input, final TypeReference<MyResponse<T>> typeRef) { return convert(content, typeRef).values(); } 

which must be called as:

final List<MyStringValue> values = unwrap(input, new TypeReference<MyResponse<MyResult<MyStringValue>>>() {}); // or with diamond operator: final List<AnotherType> values = unwrap(input, new TypeReference<>() {}); 

Callers of unwrap should not need to know about MyResponse nor MyResult. Is it possible to define unwrap in such a way that these implementation details are hidden?

final List<MyStringValue> values = unwrap(input, new TypeReference<MyStringValue>() {}); <T> List<T> unwrap(final String input, final TypeReference<T> typeRef) { // <- callers do not need to know about MyResponse/MyResult final TypeReference<MyResponse<MyResult<T>>> wrappedTypeRef = typeRef.wrap(MyResponse<MyResult<T>>.class); // obviously not valid Java syntax return convert(content, typeRef).values(); } 

or am I simply stuck with exposing the full type to all callers of this method due to type erasure?

(Both MyResponse and <T> will be different concrete types in the real code: MyResponse has a parent/child class and T has 3 implementations)

7
  • The more "powerful" version of TypeReference is JavaType. Can you change convert to take a JavaType as parameter instead? Commented Nov 13, 2024 at 16:03
  • Maybe your case is a bit different from what I understood, but since you already know the three possible implementations of T, wouldn't it be possible to find a common upper bound and define a custom deserializer like shown in this answer stackoverflow.com/questions/78684321/…? Commented Nov 13, 2024 at 16:03
  • @dani-vta more implementations might be added in the future, and I'd like a flexible (but simple) solution if possible Commented Nov 13, 2024 at 16:13
  • @Sweeper thanks, I'll have a look if JavaType offers a solution to my problem Commented Nov 13, 2024 at 16:13
  • It does. All you need is to call TypeFactory.constructParametricType to wrap some existing type into another generic type. It really just depends on whether you can change convert's parameter type, or add another overload of convert. Commented Nov 13, 2024 at 16:16

1 Answer 1

2

TypeReferences are designed solely to be created by subclassing it.

You can add a new overload of convert that takes a JavaType, which you can dynamically create using TypeFactory.

<T> MyResult<T> convert(final String input, final JavaType typeRef) { try { return objectMapper.<MyResponse<T>>readValue(input, typeRef).result(); } catch (final JsonProcessingException ex) { throw new RuntimeException(ex); } } <T> MyResult<T> convert(final String input, final TypeReference<MyResponse<T>> typeRef) { return convert(input, TypeFactory.defaultInstance().constructType(typeRef)); } 

Use constructParametricType in unwrap to create the type you want:

<T> List<T> unwrap(final String input, final TypeReference<T> typeRef) { var typeFactory = TypeFactory.defaultInstance(); // this creates a MyResponse<MyResult<T>> // though are you sure you want this type instead of MyResponse<T>? var responseResultType = typeFactory.constructParametricType( MyResponse.class, typeFactory.constructParametricType( MyResult.class, typeFactory.constructType(typeRef) ) ); return this.<T>convert(input, responseResultType).values(); } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.