This is not the final answer to your problem. But I hope this gives the direction you might want to continue with. Note that, this direction modifies bytecode instructions at runtime and can cause crashes and failures.
You can use javassist
which is a bytecode manipulator. Please see their official site for more info.
Two important things to note
In Java, multiple class loaders can coexist and each class loader creates its own name space. Different class loaders can load different class files with the same class name
The JVM does not allow dynamically reloading a class. Once a class loader loads a class, it cannot reload a modified version of that class during runtime. Thus, you cannot alter the definition of a class after the JVM loads it. However, the JPDA (Java Platform Debugger Architecture) provides limited ability for reloading a class
So you can achieve what you want in two different ways.
Create bytecode at runtime, write the class, use a custom classloader to create your pojo from the written class. javassist
can help you for this, but this is way too complicated for me to consume at the moment.
Use javassist
to modify an existing class and use reflection to set the values.
For option 2, the easier one, here is how you can achieve this.
- Add
javassist
in your classpath. If you are using maven, add the following dependency in your pom.xml
.
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
- Create a dummy empty pojo class that you need to work with. Let us call it
Pojo
.
package com.test;
public class Pojo {
//Nothing in the source file.
}
- Modify the class body to add the fields from the
HashMap
. Here is a sample of how I did it using the map you gave.
Map<String, String> map = new HashMap<String, String>();
map.put("firstname", "John");
map.put("lastname", "Doe");
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("com.test.Pojo");
// Used for non-primitive data types. If primitive, use CtClass.<inttype, floattype, etc..>
CtClass strClass = ClassPool.getDefault().get("java.lang.String");
//Iterate and add all the fields as per the keys in the map
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
CtField field = new CtField(strClass, key, cc);
field.setModifiers(Modifier.PUBLIC);
cc.addField(field);
}
// Instantiate from the updated class
Class<Pojo> clazz = cc.toClass();
Pojo newInstance = clazz.newInstance();
//Use the map again to set the values using reflection.
iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
newInstance.getClass().getField(key).set(newInstance, map.get(key));
}
newInstance
is the instance of Pojo
but with fields added based on keys of the map and set based on the values in the map. A simple test to print the newInstance
using jackson ObjectMapper
yields this.
ObjectMapper objMapper = new ObjectMapper();
String writeValueAsString = objMapper.writeValueAsString(newInstance);
System.out.println(writeValueAsString);
{"firstname":"John","lastname":"Doe"}
Hope this helps.
EDIT
If you want to add get/set methods, you can create methods using CtMethod
in javassist
. However, you can only access them using reflection since these methods are added at runtime.