0

At runtime I want to create a POJO with attributes that are same as keys of the map and populate it with the values of the map.
I don't know the content of the map or what the POJO will look like.
Example-

Map<String,String> map = new HashMap<>();
map.add("attr1", obj1);
map.add("attr2", obj2);
...

From this map I want to create a POJO-

class POJO
{
    String attr1;

    public void setAttr1(String attr1) {
        this.attr1 = attr1;
    }

    public String getAttr1() {
        return attr1;
    }

    String attr2;

    public void setAttr2(String attr2) {
        this.attr2 = attr2;
    }

    public String getAttr2() {
        return attr2;
    }

    .....
}

and populate it as well. All of this should happen at runtime. Something like-

Object object = getPopulatedPOJO(map)

or

Class type = getPOJOType(map);
Object object = type.newInstance();
object = getPopulatedPOJO(map)
Farrukh Chishti
  • 7,652
  • 10
  • 36
  • 60

2 Answers2

1

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

  1. 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

  2. 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.

  1. 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.

  2. 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.

  1. 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>
  1. 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.
}
  1. 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));
        }
  1. 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.

Community
  • 1
  • 1
aksappy
  • 3,400
  • 3
  • 23
  • 49
0

See the answer in a similar question, using Jackson objectMapper.convertvalue method seems most reasonable.

gbuys
  • 255
  • 3
  • 9