Construct type for class implementing List bound to specific generic type

1,191 views
Skip to first unread message

thorben....@camunda.com

unread,
Jan 31, 2017, 2:27:16 PM1/31/17
to jackson-user
Hi,

Jackson-databind version: 2.8.6

I have a class that implements java.util.List with a compile-time defined generic type, e.g.

public static class WrapperList implements List<Byte>
{
   
// delegate to methods of a wrapped ArrayList
}

Now I want to use TypeFactory to create a canonical String representation of that type that I can persist and later use for (de-)serialization, e.g.

WrapperList list = new WrapperList();
list
.add((byte) 10);

ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory = mapper.getTypeFactory();
JavaType type = typeFactory.constructType(list.getClass());
String canonicalTypeName = type.toCanonical();

This results in canonicalTypeName being org.camunda.bpm.Main$WrapperList<java.lang.Byte>. However, calling

typeFactory.constructFromCanonical(canonicalTypeName);

results in the following exception:

Exception in thread "main" java.lang.IllegalArgumentException: Can not create TypeBindings for class org.camunda.bpm.Main$WrapperList with 1 type parameter: class expects 0
    at com.fasterxml.jackson.databind.type.TypeBindings.create(TypeBindings.java:125)
    at com.fasterxml.jackson.databind.type.TypeBindings.create(TypeBindings.java:95)
    at com.fasterxml.jackson.databind.type.TypeBindings.create(TypeBindings.java:86)
    at com.fasterxml.jackson.databind.type.TypeParser.parseType(TypeParser.java:54)
    at com.fasterxml.jackson.databind.type.TypeParser.parse(TypeParser.java:33)
    at com.fasterxml.jackson.databind.type.TypeFactory.constructFromCanonical(TypeFactory.java:544)
    at com.example.Main.main(Main.java:42)

Am I using the wrong method for creating the JavaType in this case?

Cheers,
Thorben

PS: Here's the full code example for reference:

package com.example;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;

public class Main {

  public static void main(String[] args) throws IOException {
    WrapperList list = new WrapperList();
    list.add((byte) 10);

    ObjectMapper mapper = new ObjectMapper();
    TypeFactory typeFactory = mapper.getTypeFactory();
    JavaType type = typeFactory.constructType(list.getClass());
    System.out.println(type.toCanonical());

    JavaType reconstructedType = typeFactory.constructFromCanonical(type.toCanonical());
  }

  public static class WrapperList implements List<Byte>
  {
    protected List<Byte> innerList = new ArrayList<Byte>();

    public int size() {
      return innerList.size();
    }


    public boolean isEmpty() {
      return innerList.isEmpty();
    }


    public boolean contains(Object o) {
      return innerList.contains(o);
    }


    public Iterator<Byte> iterator() {
      return innerList.iterator();
    }


    public Object[] toArray() {
      return innerList.toArray();
    }


    public <T> T[] toArray(T[] a) {
      return innerList.toArray(a);
    }


    public boolean add(Byte e) {
      return innerList.add(e);
    }


    public boolean remove(Object o) {
      return innerList.remove(o);
    }


    public boolean containsAll(Collection<?> c) {
      return innerList.containsAll(c);
    }


    public boolean addAll(Collection<? extends Byte> c) {
      return innerList.addAll(c);
    }


    public boolean addAll(int index, Collection<? extends Byte> c) {
      return innerList.addAll(index, c);
    }


    public boolean removeAll(Collection<?> c) {
      return innerList.removeAll(c);
    }


    public boolean retainAll(Collection<?> c) {
      return innerList.retainAll(c);
    }


    public void clear() {
      innerList.clear();
    }


    public boolean equals(Object o) {
      return innerList.equals(o);
    }


    public int hashCode() {
      return innerList.hashCode();
    }


    public Byte get(int index) {
      return innerList.get(index);
    }


    public Byte set(int index, Byte element) {
      return innerList.set(index, element);
    }


    public void add(int index, Byte element) {
      innerList.add(index, element);
    }


    public Byte remove(int index) {
      return innerList.remove(index);
    }

    public int indexOf(Object o) {
      return innerList.indexOf(o);
    }


    public int lastIndexOf(Object o) {
      return innerList.lastIndexOf(o);
    }


    public ListIterator<Byte> listIterator() {
      return innerList.listIterator();
    }


    public ListIterator<Byte> listIterator(int index) {
      return innerList.listIterator(index);
    }


    public List<Byte> subList(int fromIndex, int toIndex) {
      return innerList.subList(fromIndex, toIndex);
    }
  }

}

Tatu Saloranta

unread,
Feb 2, 2017, 11:21:32 AM2/2/17
to jackson-user
Ok, code has a few problems.

First: method `constructFromCanonical()` really isn't meant to be
end-user functionality.
Maybe Javadocs should make this clear, but it is not something I was
planning to be used by anything but core Jackson functionality. I can
sort of see why it might seem useful, but there is one big problem
using it for persisting generic type...

The problem is this: all instances of generic types, at runtime, are
subject to type erasure.
So you will not be able to obtain any more information than actual
underlying `Class<?>`.
This being the case you might as well just access `class.getName()`
and use that.
This is different from programmatically constructed `JavaType`
instances which can and do retain generic type information; but this
comes from property type (and class super type) declarations.

So use of:

JavaType type = typeFactory.constructType(list.getClass());

tends not to work as expected: none of generic types are preserved.

In this case it is also possible that canonical name handling isn't
working like it should (maybe worth filing a bug for), since it should
always be possible to read back canonical name you created.
However, I don't think it would work for your use case: it can not be
any better than simply serializing name of the `Class` you have, due
to type erasure.

Going back to persisting type externally: you should either serialize
simple class name (safest), or, if you absolutely need generic typing,
programmatically construct generic type using `TypeFactory`, and then
get canonical name.

I hope this helps,

-+ Tatu +-
> --
> You received this message because you are subscribed to the Google Groups
> "jackson-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to jackson-user...@googlegroups.com.
> To post to this group, send email to jackso...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

thorben....@camunda.com

unread,
Feb 3, 2017, 5:59:25 AM2/3/17
to jackson-user
Hi Tatu,

Thanks for your response and good to know that I am using something that isn't intended to be used :)

I'll elaborate a little bit on my actual use case and then ask some follow-up questions.

My use case is: I'd like to have code that takes instances of classes implementing java.util.List, serializes them as JSON and stores the JSON along with the instances type name such that I can deserialize the list at a later point in time. I'd like the JSON to contain as little type-metadata as possible. For a regular java.util.ArrayList I use this code:

List<?> someList = ..;
String typeName;
if (!someList.isEmpty()) {
 
Object firstElement = list.get(0);
 
typeName = typeFactory.constructCollectionType(someList.getClass(), firstElement);
}
else
{
  typeName = typeFactory.constructType(someList.getClass()); // or could simply use Class#getName
}

This code circumvents the type erasure problem. Of course it has the limitations that it cannot deal with lists that contain instances of different classes or lists that contain lists again. These limitations are ok for my use case.

For deserialization, I use the aforementioned `constructFromCanonical()`.

With jackson-databind 2.6.3, this code works fine for both instances of java.util.ArrayList or classes like WrapperList in my first post. With Jackson 2.8.6 this approach fails for WrapperList.

Now my questions would be:

1. Can my use case (serialization of JSON, serialization of type, avoiding type information in JSON) be solved with Jackson end-user features?
2. If no to 1), would I have to lift the restriction of avoiding type information in the JSON?
3. If in general yes to 1), what would be the end-user way to get from a canonical type string and JSON back to a deserialized object?

Cheers,
Thorben

Michael Nielson

unread,
Aug 9, 2017, 4:41:30 PM8/9/17
to jackson-user
Hi Thorben, were you able to find a solution to the problem? I'm running into the same issue trying to run camunda in an application with dependencies on Jackson 2.8 and not having a lot of luck.

thorben....@camunda.com

unread,
Sep 25, 2017, 3:33:55 AM9/25/17
to jackson-user
Hi Michael,

Sorry for the late response. I haven't looked into this any further so far. You can ask us to update the Jackson dependency in the course of which we would have to look into this in more detail. For that, go to the Camunda JIRA: https://app.camunda.com/jira/browse/CAM

Cheers,
Thorben
Reply all
Reply to author
Forward
0 new messages