1

After failing miserably trying to use TypeTools Resolving generic type information with TypeTools I am attempting to use https://github.com/cowtowncoder/java-classmate instead.

Can someone help me fix this code?

public T fromMap(S map) {
    TypeResolver typeResolver = new TypeResolver();        
    ResolvedType type = typeResolver.resolve((new MapperImpl<T, S>() {}).getClass());
    List<ResolvedType> params = type.typeParametersFor(MapperImpl.class);
    ResolvedType typeT = params.get(0);

    ObjectMapper objectMapper = new ObjectMapper();
    T obj = objectMapper.convertValue(map, (Class<T>) typeT.getErasedType());
    return obj;

}

I am getting this error:

java.util.LinkedHashMap cannot be cast to LoginInputMapTest$Foo java.lang.ClassCastException at shouldMapToFoo(LoginInputMapTest.java:83)

with this minimal test case:

public static class Foo {
       private String a;

        public String getA() {
            return a;
        }

        public void setA(String a) {
            this.a = a;
        }

   }

   @Test
   public void shouldMapToFoo() {
       Map<String, Object> map = new HashMap<>();
       map.put("a", "aaa");

       Mapper<Foo, Map<String, Object>> mapper = new MapperImpl<>();
       Foo foo = mapper.fromMap(map);
       Assert.assertEquals(foo.getA(), map.get("a"));
   }
Community
  • 1
  • 1
dmz73
  • 1,588
  • 4
  • 20
  • 32
  • Does your MapperImpl do something? Looks like you created an anonymous class without actually accessing it. Or is this just an abbreviation? – SME_Dev Oct 07 '14 at 14:58
  • The anonymous class is there to provide a subtype. I think you need it to resolve the generic type. See this example: https://github.com/cowtowncoder/java-classmate#resolving-type-parameters-for-a-class – dmz73 Oct 07 '14 at 15:04

2 Answers2

3

There's nothing you can do within your fromMap method to get the type argument provided that was bound to your type variable T.

I suggest you create a Mapper implementation specifically for Foo.

class FooMapperImpl<S> implements Mapper<Foo, S> {
    public Foo fromMap(S map) {
        ObjectMapper objectMapper = new ObjectMapper();
        Foo obj = objectMapper
                .convertValue(map, Foo.class);
        return obj;
    }
}

(Though I don't see why you need a source type S if it's always going to be a Map.)

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • You're right there is no need to parameterize the Mapper interface with S. Why do you say there is nothing I can do to get the type T. Isn't that the purpose of libraries like TypeTools and classmate? – dmz73 Oct 07 '14 at 17:13
  • What I am trying to do is exactly what they describe here in "4. Real world usage" http://www.cowtowncoder.com/blog/archives/2012/04/entry_471.html – dmz73 Oct 07 '14 at 17:24
  • @dmz73 That seems completely different. They can extract the type from `handler`. But your `fromMap` method has nothing which can be used to extract the type argument provided for `T`. – Sotirios Delimanolis Oct 08 '14 at 01:04
1

It seems to me that you do not fully understand the way Java generic types work, with respect to type variables (T, S). A good place to learn more about this is:

http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html

but basically type variables do not carry any run time generic type information. So while you are nominally calling a method with certain parameterization, nothing happens unless you pass actual Class instance suitable parameterized. So your method compiled to bytecode is little more than:

public Object fromMap(Object map) { ... }

Now, if you pass a Map as map, runtime type will be simply Map.class and there are no type parameters specified: Java values do not have any runtime type parameterization information. Underlying class is the same between, say, Map<String,Number> and Map<UUID,byte[]>. Declarations of parameters only affect Java compiler, which adds necessary casts to ensure that value types get cast properly.

No library can find information that is there, unfortunately. So usage as you suggest is not possible to implement as-is.

This does not mean that you could not pass typing, but it means that it must be passed from outside. With basic Jackson, you have TypeReference you can use:

new TypeReference<Map<KeyType, ValueType>>() { };

would construct reference to type Map<KeyType,ValueType>. Or you can construct these programmatically using TypeFactory; something like:

mapper.getTypeFactory().constructMapType(Map.class, KeyType.class, ValueType.class);
// or with recursively constructing nested generic types

Now: ClassMate can, conversely, extract type information out of class definitions. If you have class with fields, methods that use generic type declaration, it is difficult to easily find out declared parameterization. But it does not sound like this is what you actually want or need here. Rather you should be able to build it using Jackson's type handling functionality.

StaxMan
  • 113,358
  • 34
  • 211
  • 239