Libraries like Jackson can create objects from JSON if we provide enough information about generics.
In Jackson, we can do
Class<?>[] parameterTypes;
JavaType type = objectMapper.getTypeFactory().constructParametricType(ObjectClass, parameterTypes);
objectMapper.readValue(json, type);
In java, a Generic class can be defined in many ways, like one generic class having another generic class and that can have another generic class, for simple illustration consider these three classes.
public class MultiLevelGenericTestData<T, V> {
private GenericTestData<T> tGenericTestData;
private GenericTestData<V> vGenericTestData;
}
public class MultiGenericTestData<K, V> {
private K key;
private V value;
}
public class MultiGenericTestDataSameType<T> {
private GenericTestData<T> genericTestData;
private MultiGenericTestData<T, T> multiGenericTestData;
}
I know about type erasure and other things but is there a way to identify the type T, V
from the object of MultiLevelGenericTestData
?
One way I thought of check generic types and look at their name and inspect all the fields until you have found all the types. This quickly becomes tricky as soon as we hit the case where there's more than one field with the same generic type, for example in MultiGenericTestDataSameType
, we should get only one generic type.
// This method should find all type's class names in the list
// that can be used to construct the object without any issue.
void genericFieldClassNames(List<String> types, List<String> classes, Object payload)
throws IllegalAccessException {
for (Field field : payload.getClass().getDeclaredFields()) {
// ignorefield without annotation
if (!field.isAnnotationPresent(GenericField.class)) {
continue;
}
Type genericType = field.getGenericType();
// not a generic field
if (genericType.equals(field.getType())) {
continue;
}
// null value nothing can be done
Object fieldVal = FieldUtils.readField(field, payload, true);
if (fieldVal == null) {
continue;
}
String genericFieldType = genericType.getTypeName();
Class<?> fieldClass = fieldVal.getClass();
// problematic cases when we start traversing up
if (genericFieldType.endsWith(">")) {
genericFieldClassNames(types, classes, fieldVal);
} else {
// here a check can be added to avoid duplicate type name but as soon as
// we add type genericFieldType check it will fail when we have used similar
// types in construction like MultiGenericTestData<String, String>
types.add(genericFieldType);
classes.add(fieldClass.getName());
}
}
}
The number of type parameters can be found via the method getTypeParameters
, how can we combine this to get exact type information.
Example
MultiLevelGenericTestData<String, String> data;
In this case, we should get [String, String]
MultiLevelGenericTestData<String, Integer> data;
In this case, we should get [String, Integer]
MultiGenericTestData<String, String> data;
In this case, we should get [String, String]
MultiGenericTestDataSameType<String> data;
In this case, we should get [String]
This becomes even more interesting when type T itself is generic for example
MultiGenericTestDataSameType< MultiGenericTestData< String, Integer> > data;
For this data, we should get MultiGenericTestData
and it's generic parameters String
and Integer
.
Edit:
For further clarification, I would like to get type information without creating any additional class and that should be passed to the serializer i.e I don't want to change my serializer method signature that looks something similar to this []byte serialize(Object payload)
. We can create as many helper classes we need, also it can be made mandatory to extend payload class from some superclass, (superclass can have logic to extract generic information).