Java - recursively modify object values with reflection

Stanos :

I would like to transform every String property of an Object (along with its nested objects) and I am using the following recursive method to achieve that with reflection API:

    public static void reflect(Object obj) {
        if (obj == null) {
            return;
        }
        Class klazz = obj.getClass();
        if (klazz.isPrimitive()
                || obj instanceof Integer
                || obj instanceof Double
                || obj instanceof Boolean)
            return;
        else {
            try {
                for (Field field : klazz.getDeclaredFields()) {
                    field.setAccessible(true);
                    Object f = field.get(obj);
                    if(f instanceof String) {
                        f = transform(f);
                        field.set(obj, f);
                    }
                    else {
                        reflect(f);
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
    }

    private static Object transform(Object f) {
        f = f + "blabla";
        return f;
    }


@Data
@Builder
public class PrintObject {
    private String field1;
    private String field2;
    private String field3;
    private NestedObject field4;
}

@Data
@Builder
public class NestedObject {
    private String field1;
    private String field2;
    private Integer field3;
}

NestedObject nestedObject = NestedObject
                .builder()
                .field1("test")
                .field2("test2")
                .field3(1)
                .build();

PrintObject printObject = PrintObject
      .builder()
      .field1("test")
      .field2("Test")
      .field3("test")
      .field4(nestedObject)
      .build();

Utils.reflect(printObject);

Up to this point every works fine and if I execute this, then all String values are appended with "blabla" in the end. Problem comes if PrintObject has other data structures like List, or Map. For example if there is another field in PrintObject class:

private List<String> field5;

then this code execution would throw StackOverflowError.

List<String> list = new ArrayList<>();
list.add("test");

NestedObject nestedObject = NestedObject
                .builder()
                .field1("test")
                .field2("test2")
                .field3(1)
                .build();

PrintObject printObject = PrintObject
      .builder()
      .field1("test")
      .field2("Test")
      .field3("test")
      .field4(nestedObject)
      .field5(list)
      .build();

Utils.reflect(printObject);

Any idea on how to make this work with these structures as well? Thanks in advance.

field5 could also be for example:

 Map<String,String>

or even

 List<List<String>>
Matt Champion :

ArrayList contains a long serialVersionUID field to help with serialisation. When you get the value it returns a boxed Long. Calling getDeclaredFields on Long returns an array containing the field Long.MIN_VALUE which is a Long. That's where the infinite loop comes from.

To resolve it I would add special case handling for Long like you do for Integer. You should also consider all the other boxed primitives like Float and Byte.

Collections will be backed either by structures that refer to each other link LinkedList or by arrays. For linked structures the code would traverse them. To support array backed collected you need to identify which fields are arrays and iterate over them.

The type of a field and be obtained with Field.getType. Arrays can be identified by Class.isArray. Arrays for different types have different types, they are not non-reified like Java generics. Arrays of non-primitive values can be cast to Object[] that is useful in this case but it is not type safe. To get the type of object in the array Class.getComponentType can be used.

Something like the following would be needed to recurse on the entries of an array.

final Class<?> fieldType = field.getType();
if (fieldType.isArray() && !fieldType.getComponentType().isPrimitive()) {
    Object[] fs = (Object[]) f;
    for (Object fi : fs) {
        reflect(fi);
    }
}

The other problem is cyclic references that could cause further StackOverflowException. If a list was added as a member to itself it would recurse infinitely. It is necessary to track previously visited object and not visit them twice. Ideally this would use an IdentityHashMap as you care about instances of objects not their equality.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=105874&siteId=1