Get List of Classes from an object in java - java

Iam currently trying to create a distinct List<Class> classList which contains all Classes of an object for example
DemoObject.java
public class DemoObject {
private Integer id;
private String name;
private BigDecimal price;
private Boolean isActive;
private List<NestedDemoObject> nested;
}
NestedDemoObject.java
public class NestedDemoObject {
private Integer id;
private String nameNest;
private Boolean isActive;
}
What i want to create is a method public List<Class> getDistinctClasses(Class cl); which you give as input for example DemoObject.class and returns a list with
[DemoObject.class, Integer.class, String.class, BigDecimal.class, Boolean.class, List.class, NestedDemoObject.class]
Another example for NestedDemoObject.class would be
[NestedDemoObject.class, Integer.class, String.class, Boolean.class]
I tried to use the .getDeclaredClasses() from Class without any luck.
There is any way to get all nested classes from an object with Reflection API?
Any help or direction appreciated.

The solution provided by Mark is partially correct. You're on the right way trying to retrieve the classes from declared fields. However getType() method does not reveal the generic types.
In order to access the generic types you should use Field.getGenericType() instead. It returns the classes as Type objects. The Field objects DO KNOW their own types (they are not erased as one may believe mistakenly).
This is a java 1.8+ example printing the types with generics:
Arrays.stream(DemoObject.class.getDeclaredFields())
.map(Field::getGenericType)
.map(Type::getTypeName)
.distinct()
.forEach(System.out::println);
It will print the following result:
java.lang.Integer
java.lang.String
java.math.BigDecimal
java.lang.Boolean
java.util.List<com.eto.sandbox.NestedDemoObject>
If you want to play with generic types or parse them for any reason then you could use this example:
Arrays.stream(DemoObject.class.getDeclaredFields())
.map(Field::getGenericType)
.distinct()
.forEach(type -> {
if (type instanceof Class) {
// This is a simple class
} else if (type instanceof ParameterizedType) {
// This is a generic type. You can parse its parameters recursively.
}
});

Maybe this points you in the right direction:
for (Field f : DemoObject.class.getDeclaredFields()) {
System.out.println(f.getType().getName());
}
This prints:
java.lang.Integer
java.lang.String
java.math.BigDecimal
java.lang.Boolean
java.util.List
You can get a class instance through something like Class.forName.
I find it odd that getDeclaredClasses is not working for me either, and I will look into that. I'll update the answer when I know more.
UPDATE
getDeclaredClasses prints classes defined inside a class like so:
class DemoObject {
private Integer id;
private String name;
private BigDecimal price;
private Boolean isActive;
private List<NestedDemoObject> nested;
public class InnerClass {
}
}
Then executing getDeclaredClasses:
for (Class<?> f : DemoObject.class.getDeclaredClasses()) {
System.out.println(f.getName());
}
prints the value:
DemoObject$InnerClass

Related

Object not being able to map to POJO class

I am getting a response, which I converted to Pojo class with one field of type Object. Now when I am trying to cast the Object type to another Pojo class its throwing the error :
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to SecondClass
Code :
FirstClassResponse firstClassResponse = (FirstClassResponse) convertJSONToObject(firstClassResponseJson, FirstClassResponse.class);
//jsonToObject method
public static Object convertJSONToObject(String jsonRequest, Class objectClassType) throws Exception {
Object object = gson.fromJson(jsonRequest, objectClassType);
return object;
}
Here, firsClass object when printed gives following result :
FirstClassResponse [modifiedResponse=null, response={id=123, username=abc, balance=0.0, currencycode=EUR, created=2021-03-30 16:31:54, agent_balance=0.0, sessionid=123}]
Now, the error happens in the following line :
SecondClassResponse modifiedResponse = (SecondClassResponse) firstClassResponse.getResponse();
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to SecondClassResponse
I am sharing the POJO for FirstClassResponse and SecondClassResponse :
public class FirstClassResponse{
private SecondClassResponse modifiedResponse;
private Object response;
//getter, setter
}
public class SecondClassResponse{
private String id;
private String username;
private double balance;
private String currencycode;
private String created;
private double agent_balance;
private String sessionid;
//getter, setter
}
private Object response;
Make this a SecondClassResponse, not an Object. With it being an Object, GSON doesn't know that this should be a SecondClassResponse, so it just shoves the map in there as a Map, which obviously can't be cast.
The entire point of using GSON is to turn everything into specific objects so you can use it in a more Java like way. If you store something as an Object when converting from GSON, you're almost always doing it wrong.
That FirstClassResponse is completely superfluous; use SecondClassResponse instead.
Just look at the JSON ...and then explain to me how to map as FirstClassResponse?
And you've not even object-relational mapping (as the GSON converter does), but you're parsing.
Perhaps gson.fromJson cannot convert the attribute class of the class before. You can try to take out firtClassResponse.getResponse() and do the conversion separately

Jackson deserialize "simple type" to enum

I have a common class
public CommonClass {
private Enum<?> field1;
private String field2;
private String field3;
private Map<? extends Enum<?>, Map<String, Object>> map;
// constructor, getters setters
}
And two enums
public enum A {
FIELD,
VALUE
}
public enum B {
ENUM1,
ENUM1
}
And json to parse in it class
{
"field1": "FIELD",
"field2": "field2",
"field3": "field2",
"map": {
"ENUM1": {
// some inner data
},
"ENUM2":{
// some inner data
}
}
}
When I try to parse this JSON to an object I got an exception
java.lang.IllegalArgumentException: No enum constants for class java.lang.Enum
This exception occurs when Jackson tried to deserialize "ENUM1" which is a ? extends Enum<?> to B
When I debug it, Jackson thought that this property is simple type.
Is any suggestion why it doesn't work?
I think you cannot deserialize even this simplified one by default:
public class CommonClass {
private Enum<?> field1;
}
I is because no Enum nor enum has public no args constructor. To serialize beforementioned is easy. But to deserialize you need to know which enum and which value. So if you had:
public class CommonClass {
private A field1;
}
It would work because Jackson sees the enum is of type A and - I guess - makes it like:
A.valueOf("FIELD");
And as you see here A is static. Enums are static and also their values are final.
If you want to deserialize arbitrary generic enum value Enum<?> you need to have custom deserializer that determines the enum type and together with value and gets instance of enum like above. And you might also need a custom serializer that serializes also the type for custom deserializer to read.

Jackson deserialisation mappings to Java Generics

What I am trying to do is the following: Given a JSON document, map it to a POJO using Jackson, but define the type of the Generic class member based on a field in the JSON document.
My JSON looks as follows
{
"name": "Name",
"parameters": [
{"name": "paramName","value": "Value1", "#type": "string"},
{"name": "size","value": 5,"#type": "double"}
]
}
The class that maps to this JSON doc is
public class Strategy {
public String name;
public List<Parameter<?>> parameters;
}
Then I have a Generic class for this as follows
public class Parameter<T> {
public String name;
public T value;
#Override
public String toString() {
return this.getClass().getName();
}
}
So the idea is to tell Jackson when you deserialize the JSON document into the Strategy class and get to the parameters field, use the following classes as the Generic data type for the value member, i.e. I want to select it to be String or Double or Integer but I want that to be my decision so that it's generic and can be extended to any data type I want.
I realise I can use the annotation JsonTypeInfo which I added as well like this
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="#type")
But using these classes as is actually works but Jackson decides itself what the type should be based on its value and my size parameter is set to an Integer. If I set its value to 5.0 then its set to a Double which works, but what if I want one of the parameters to be a custom object?
The only way I could get this to work (and am not 100% happy with the solution) is to make the Parameter class abstract and then create concrete classes for each type that I want, i.e. ParameterString, ParameterDouble, ParameterCustomClass and then use the #JsonSubTypes annotations to set the correct class to use based on the type field in the JSON document.
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="#type")
#JsonSubTypes({
#JsonSubTypes.Type(value=ParameterString.class, name="string"),
#JsonSubTypes.Type(value=ParameterDouble.class, name="double"),
#JsonSubTypes.Type(value=ParameterInstrument.class, name="instrument")
})
With the following class as an example
public class StrategyParameterString extends StrategyParameter<String> {
}
This isn't very extendable, I guess it will just need a new subtype annotation and concrete class added for every type that I need, but just doesn't feel as elegant as it could be.
Does anyone know of a better way of handling this ?
Thanks
Andrew
As I understand it, the types you want to represent in your Parameter list are reifiable, eg. String, Double, Instrument. You can take advantage of the fact that reifiable types have a runtime type token in the form of their class literal. This can be exploited to form the basis of a heterogenous type safe collection.
Instead of defining your Parameter class this way:
public class Parameter<T> {
public String name;
public T value;
:
:
}
}
You can define it as a concrete class that associates the object's value with its run time type token.
public class Parameter() {
private final Object m_value;
private final Class<?> m_cls;
private Parameter(Class<?> token, Object val) {
m_value = val;
m_cls = token;
}
public static <T> Parameter newInstance(Class<T> token, T value) {
return new Parameter(token, value);
}
:
:
public <T> T getValue(Class<T> token) {
if (token != m_cls) throw new ClassCastException("Type error");
return token.cast(m_value);
}
}
In this setting, type tokens and generic methods (rather than a generic type) are used to set and reestablish the type linkage for the desired value. The value you set can be any type and is guaranteed to be returned as the same type that you stored as long as the type tokens are consistent.
Note that constructors can not be generic. To address this, the constructor for Parameter has been made private and Parameter instances are formed by invoking the newInstance() static factory method (which can be generic).

Create a generic map using type given at runtime

The title may be a bit hard to understand, but let me just briefly describe my problem.
Let's assume I have an annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Identifier {
}
Now, I make a class which annotates any of its fields with it:
public class Student {
private String name;
private String surname;
#Identifier
private String idNumber;
...
}
Finally, at runtime I want to create a Map with the key type of typeof(field annotated with #Identifier) and the value type of Student. Note that any field can be annotated with #Identifier.
Any ideas?
EDIT
Ok, let me clarify this a bit:
class Student {
private String name;
private String surname;
#Identifier
private String idNumber;
}
class Foo {
#Identifier
private Integer x;
}
// Now, what I want to have are two maps:
SortedMap students; // key type: String
// value type: Student
SortedMap foos; // key type: Integer
// value type: Foo
Thanks in advance!
I'm still not exactly sure what you want to do.
at runtime I want to create a Map with the key type of typeof(field
annotated with #Identifier) and the value type of Student
You can create a raw Map or a Map<Object, Object>. You can get the type of the field annotated with #Identifier. I'm not sure what you mean by value type of Student so I'll assume you mean the type Student, ie. its Class object.
public static void main(String[] args) throws Exception {
Class<?> clazz = Student.class;
Map<Object, Object> map = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Identifier annotation = field.getAnnotation(Identifier.class);
if (annotation != null) {
map.put(field.getType(), clazz);
}
}
System.out.println(map);
}
With your example class in your question, this prints
{class java.lang.String=class com.spring.Student}
So the annotated field type is mapped to the class type.
You won't be able to have a Map<String,Student> though because you don't know the type String (and possibly not even Student) at compile time. You can try casting, but you're setting yourself up for a number of ClassCastExceptions.
So you are going to have a method (myMethod in my example) which will be passed objects which may hold a field annotated with #Identifier.
Sorry to burst your bubble but there is no way to keep generic information at runtime. The closest you can get is having a Map<Field, Class<?>> which holds key-value pairs with your desired type. This is how you do it:
public Map<Field, Class<?>> myMethod(Object obj) {
Map<Field, Class<?>> result = new HashMap<Field, Class<?>>();
for(Field field : obj.getClass().getDeclaredFields()) {
Identifier identifier = field.getAnnotation(Identifier.class);
if(identifier != null) {
result.put(field, obj.getClass());
return result;
}
}
return result;
}
In my example the result will either be an empty Map or a Map with one key-value pair. I suggest you should use a separate type for the result instead of a Map. Of course you can tamper with the code if you want something other than a Field for example you can use Field's getType() or getGenericType() methods.

Instantiate generified class after loading from repository

When writing a type handler for a repository (such as a web service or a database), I need to instantiate the type after the value is loaded from the repository.
Let's say I get a String value from the repository and there is a constructor with one String argument that I can use. If the return type has a type parameter, what else can I do besides instantiating the raw type? It seems raw types exist only for compatibility with legacy code so I would prefer not to use them.
Normally ? can be used as type parameter (if you know the type will be correct at runtime), but not in this case because you can't instantiate classes with wildcards as type parameter.
EDIT: some example code:
Let's say I have a PrimaryKey class like this:
public class PrimaryKey<R extends RepositoryObject<R>> {
private String value;
public PrimaryKey(String value) {
this.value = value;
}
}
And a set of classes that extend RepositoryObject, which is defined like this:
public class RepositoryObject<R extends RepositoryObject<R>> {
private PrimaryKey<R> pk;
public RepositoryObject(PrimaryKey<R> pk) {
this.pk = pk;
}
PrimaryKey<R> getPrimaryKey() {
return pk;
}
}
Example of a subclass:
public class User extends RepositoryObject<User> {
public User(PrimaryKey<User> userId) {
super(userId);
}
}
Now the type handling method for class PrimaryKey will look something like this:
public PrimaryKey<?> getValue(String stringValue) {
return new PrimaryKey<>(stringValue);
}
But this results in a compiler error (in the Maven build, not in Eclipse IDE strangely enough) even though I'm using the diamond operator instead of when instantiating. Maybe for some reason type inference doesn't work well because of the recursion in the type parameters.
In Java 7 you can typically use the diamond operator to get around this limitation:
Container<?> c = new Container<>(arg);
Otherwise you can use a helper factory method:
<T> Container<T> makeContainer(String arg) {
return new Container<T>(arg);
}
...
Container<?> c = makeContainer(arg);
EDIT:
Following your update, I can see you're using a recursive type parameter <R extends RepositoryObject<R>>. This compile error is due to limitations of javac when it comes to wildcard capture and recursive type parameters. See this related post for example: Java CRTP and Wildcards: Code compiles in Eclipse but not `javac`
Unfortunately, using a raw type is necessary as a workaround, but it can be hidden as an implementation detail:
public PrimaryKey<?> getValue(String stringValue) {
#SuppressWarnings("rawtypes") //a raw type is necessary to placate javac
final PrimaryKey<?> pk = new PrimaryKey(stringValue);
return pk;
}
class SomeBogusClass extends RepositoryObject<SomeBogusClass> { }
return new PrimaryKey<SomeBogusClass>(stringValue);
seriously, you can put anything there that satisfies the bounds, even some bogus class that has nothing to do with your code.

Categories