Dynamically Accessing Objects in Java

Do you like Java Reflection? I don’t. I find it horrible to see that some Java developers use Java Reflection in situations where it could have been done without. However, there are situations in which Java Reflection is the only tool at hand to solve the problem. In such cases, the code using Reflection should be properly encapsulated and not be used openly within code for business functionality. One of the problems that require Reflection is dynamically accessing objects. Dynamically accessing objects is accessing properties and methods of objects with property names and method names that are not known at compile time but that are at run time present in variables. Dynamic object access is available out of the box in JavaScript. In JavaScript you can access the age property of a person object with the expressions person.age and person['age']. This is static object access. However, if the variable var contains the value 'age', you can access the age property also with the expression person[var]. This is dynamic object access.

In Java, a String value can be converted into a method call, and in particular a call to a getter, with a switch statement. But the number of cases in a switch is limited and has to be fixed at compile time. Using a solution with a switch is not really dynamic object access because it is not possible to access objects of classes that were unknown at compile time. Actually it will not occur very often that objects of unknown classes have to be accessed. But using dynamic object access in situations where static access (e.g. the switch solution) is possible might have distinct advantages. Usually a little bit type safety has to be sacrificed, but a lot can be gained by not having to write large switch statements.

Dynamic object access is possible in Java, but one has to do something for it. I have developed a reusable solution that can turn any pojo into a dynamically accessible object. Only properties that are made available via standard JavaBeans getters will be made available. Properties of nested objects are also accessible, and a straight forward dot separated path notation is used for accessing nested objects. For instance, if the object person of class Person has a property address of class Address, then the property street of Address can be dynamically accessed on the person object via the path address.street A Java pojo is made dynamically accessible by wrapping it in an object of the class DynaWrap. An instance of DynaWrap provides dynamic access to all JavaBeans properties of the wrapped object, and also provides discovery of the class name of the wrapped object and discovery of all paths that lead to properties or nested properties of the wrapped object. The solution, which is of course an instance of the Adapter design pattern, is in my view very concise and elegant. Whenever the need for dynamic object access arises, one should use a solution similar to this one, that has all Java Reflection neatly encapsulated in the library class. In my view such a solution for dynamic access to properties should be available in the JDK. Maybe there is something similar to this in the JDK. I have not been able to find it though. Please let me know where I can find it if there is something like DynaWrapin the JDK.

Below you you will find the source code of DynaWrap. Source code with tests are also made available as a maven project on Github.

package nl.kennemersoft;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class DynaWrap {

    private T obj;

    public DynaWrap(T obj) {
        this.obj = obj;
    }

    public Object get(String path) {
        String[] steps = path.split("\\.");
        Object result = this.obj;
        try {
            for (String step : steps) {
                Method method = result.getClass().getMethod(createGetterName(step));
                result = method.invoke(result);
            }
            return result;
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public String discoverClassName() {
        return this.obj.getClass().getName();
    }
    
    public List discoverProperties() {
        return discoverProperties(this.obj);
    }
    
    private List discoverProperties(Object obj) {
        Method[] methods = obj.getClass().getDeclaredMethods();
        List result = new ArrayList<>();
        for (Method method : methods) {
            if (method.getName().startsWith("get") && method.getParameters().length == 0) {
                String propName = createPropertyName(method.getName());
                result.add(propName);
                try {
                    if (method.invoke(obj) instanceof Object) {
                        Object subObj = method.invoke(obj);
                        List subProps = discoverProperties(subObj);
                        for (String subProp : subProps) {
                            result.add(propName + "." + subProp);
                        }
                    }
                }
                catch (Exception e) {
                    // ignore
                }
            }
        }
        return result;
    }

    private String createGetterName(String name) {
        StringBuilder sb = new StringBuilder("get");
        sb.append(name.substring(0, 1).toUpperCase());
        sb.append(name.substring(1));
        return sb.toString();
    }
    
    private String createPropertyName(String name) {
        return name.substring(3, 4).toLowerCase() + name.substring(4);
    }

    
    // convenience methods to avoid casting
    public boolean getBoolean(String path) {
        return (Boolean)get(path);
    }
    public byte getByte(String path) {
        return (Byte)get(path);
    }
    public short getShort(String path) {
        return (Short)get(path);
    }
    public int getInt(String path) {
        return (Integer)get(path);
    }
    public long getLong(String path) {
        return (Long)get(path);
    }
    public float getFloat(String path) {
        return (Float)get(path);
    }
    public double getDouble(String path) {
        return (Double)get(path);
    }
    public String getString(String path) {
        return (String)get(path);
    }

}