4

I have a nested POJO structure defined something like this,

public class Employee {
    private String id;
    private Personal personal;
    private Official official;
}

public class Personal {
    private String fName;
    private String lName;
    private String address;
}

public class Official {
    private boolean active;
    private Salary salary;
}

public class Salary {
    private double hourly;
    private double monthly;
    private double yearly;
}

I get updates from a service with dot annotaion on what value changed, for ex,

id change --> id=100
address change --> personal.address=123 Main Street 
hourly salary change --> official.salary.hourly=100

This POJO structure could be 3-4 level deeps. I need to look for this incoming change value and update the corresponding value in POJO. What's the best way of doing it?

User0911
  • 1,552
  • 5
  • 22
  • 33
  • If you are using setters (and perhaps even if not) using LambaMetafactory may lead to the speediest execution. This is something I'm not intimately familiar with, so can't provide much more information, only [this](https://stackoverflow.com/questions/19557829/faster-alternatives-to-javas-reflection) and [this](https://www.jboss.org/optaplanner/blog/2018/01/09/JavaReflectionButMuchFaster.html). Though you may need additional information about the fields (which I'd assume would be needed for input validation/filtering) – Gryphon Aug 29 '20 at 05:23

2 Answers2

1

Here's a quick approach using reflection to set fields dynamically. It surely isn't and can't be clean. If I were you, I would use a scripting engine for that (assuming it's safe to do so).

private static void setValueAt(Object target, String path, String value) 
        throws Exception {
    
    String[] fields = path.split("\\.");
    if (fields.length > 1) {
        setValueAt(readField(target, fields[0]), 
                path.substring(path.indexOf('.') + 1), value);
        return;
    }

    Field f = target.getClass()
            .getDeclaredField(path);
    f.setAccessible(true);
    f.set(target, parse(value, f.getType())); // cast or convert value first
}

//Example code for converting strings to primitives
private static Object parse(String value, Class<?> type) {
    if (String.class.equals(type)) {
        return value;
    } else if (double.class.equals(type) || Double.class.equals(type)) {
        return Long.parseLong(value);
    } else if (boolean.class.equals(type) || Boolean.class.equals(type)) {
        return Boolean.valueOf(value);
    }
    return value;// ?
}

private static Object readField(Object from, String field) throws Exception {
    Field f = from.getClass()
            .getDeclaredField(field);
    f.setAccessible(true);
    return f.get(from);
}

Just be aware that there's a lot to improve in this code (exception handling, null checks, etc.), although it seems to achieve what you're looking for (split your input on = to call setValueAt()):

Employee e = new Employee();
e.setOfficial(new Official());
e.setPersonal(new Personal());
e.getOfficial().setSalary(new Salary());

ObjectMapper mapper = new ObjectMapper();
setValueAt(e, "id", "123");
// {"id":"123","personal":{},"official":{"active":false,"salary":{"hourly":0.0,"monthly":0.0,"yearly":0.0}}}

setValueAt(e, "personal.address", "123 Main Street");
// {"id":"123","personal":{"address":"123 Main Street"},"official":{"active":false,"salary":{"hourly":0.0,"monthly":0.0,"yearly":0.0}}}

setValueAt(e, "official.salary.hourly", "100");
// {"id":"123","personal":{"address":"123 Main Street"},"official":{"active":false,"salary":{"hourly":100.0,"monthly":0.0,"yearly":0.0}}}
ernest_k
  • 44,416
  • 5
  • 53
  • 99
0

If you would like to create Java objects that allows you to edit fields. You can specify your object fields with the public/default/protected access modifiers. This will enable you to get and set fields such as personal.address or official.salary.hours

This approach is typically frowned upon as the object is no longer encapsulated and any calling methods are welcome to manipulate the object. If these fields are not encapsulated with getters and setters, your object is no longer a POJO.

public provides access from any anywhere.

default provides access from any package

protected provides access from package or subclass.

public class Employee {
    public String id;
    public Personal personal;
    public Official official;
}

public class Personal {
    public String fName;
    public String lName;
    public String address;
}
shinjw
  • 3,329
  • 3
  • 21
  • 42