5

I have a method with the following signature:

// Converts a json string to a list of objects
// Assumption: json is an array, and all items in the list are of the same type
public <T> List<T> getListFromJson( String json, Class<T> itemType, List<T> defValue ) {
    final ArrayList<T> list = new ArrayList<T>();

    for( JsonElement e : parser.parse(json).getAsJsonArray())
        list.add( (T) (
                Number.class.isAssignableFrom(itemType) ? e.getAsNumber() :
                Boolean.class.isAssignableFrom(itemType) ? e.getAsBoolean() :
                Character.class.isAssignableFrom(itemType) ? e.getAsCharacter() :
                String.class.isAssignableFrom(itemType) ? e.getAsString() :
                JsonElement.class.isAssignableFrom(itemType) ? e :
                null
            )
        );

    return list;
}

It reads the json string and converts it to a list of the appropriate type of object, eg. Integer, String, etc.

Is there a robust way to remove the Class<T> argument from the method by inferring it from the List<T> parameter? Eg. Is there a way I can change the method signature to the following without losing functionality?

public <T> List<T> getListFromJson( String json, List<T> defValue ) {
    ...
}

It seems like the solution is going to require some fancy manipulation of ParameterizedType. I've looked at the following, but either I'm using these methods incorrectly or they're not doing what I expect:

emmby
  • 99,783
  • 65
  • 191
  • 249

2 Answers2

8

Due to type erasure, you definitely can't "infer" what T is--it doesn't even exist at runtime. The closest you could come is inspect the values in defValue (if it has values) and get the class of the elements there.

Class<?> tType = defValue.get(0).getClass();

if (Boolean.class.isAssignableFrom(tType)) { //...  

Edit

With regards to your thinking of using reflection like getTypeArguments(), etc. Those only provide data for declared types, never actual types. So for example, if you got a handle to the Method object and called getTypeParameters(), you'd just get an array containing a type object representing T--not the actual type that T represents at some specific runtime invocation.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • You probably want to check for null-ness there, but the principle is sound. As for what you do with an empty List - well, does it matter what the nominal type of a non existent entry is? SOunds philosophical... – pauljwilliams Dec 09 '10 at 15:21
  • @Mark Peters But a library like Jackson actually does this. If I have a class `class Wrapper { List list;}` and another `class Wrapped { int onlyField;}` it will deserialize a JSON string representation of a `Wrapper` object into the right object types. – Arya Pourtabatabaie Apr 21 '19 at 21:54
  • 1
    @AryaPourtabatabaie: Yes, because those types are part of the *declared* type of the class' members. These static types (field types, method signatures, etc) are *reified* (included as metadata) into the compiled class files, and are required for compile and runtime linking, like method calls. These types are made available to the runtime and libraries like Jackson use it. Note the difference between your example, and one where you had `class Wrapper { List list; }` like in the question. – Mark Peters Apr 22 '19 at 04:08
1

The only way I know of to guarantee to have access to the type at run time is to make it a parameter to the object constructor thus:

class MyClass<T> {
    private final Class<T> clazz;
    public MyClass(Class<T> clazz) {
        this.clazz=clazz;
    }
}

You then have to pass the class when you instantiate the object:

MyClass<String> object = new MyClass<String>(String.class);

Obviously, in your case, you have what is effectively a static utility method and no object, so I think you're stuck with either the Class parameter or else some kind of template object.

Bill Michell
  • 8,240
  • 3
  • 28
  • 33