I need to be able to traverse through my entire object graph and log all contents of all member fields.
For example: Object A has a collection of Object B's which has a collection of Object C's and A, B, C have additional fields on them, etc.
Apache Commons ToStringBuilder is not sufficient since it won't traverse down an object graph or output contents of a collection.
Does anyone know of another library that will do this or have a code snippet that does this?
You can traverse the whole tree using org.apache.commons.lang.builder.ReflectionToStringBuilder
. The trick is that in ToStringStyle
you need to traverse into the value. ToStringStyle
will take care of values, already processed, and will not allow recursion. Here we go:
System.out.println(ReflectionToStringBuilder.toString(schema, new RecursiveToStringStyle(5))); private static class RecursiveToStringStyle extends ToStringStyle { private static final int INFINITE_DEPTH = -1; /** * Setting {@link #maxDepth} to 0 will have the same effect as using original {@link #ToStringStyle}: it will * print all 1st level values without traversing into them. Setting to 1 will traverse up to 2nd level and so * on. */ private int maxDepth; private int depth; public RecursiveToStringStyle() { this(INFINITE_DEPTH); } public RecursiveToStringStyle(int maxDepth) { setUseShortClassName(true); setUseIdentityHashCode(false); this.maxDepth = maxDepth; } @Override protected void appendDetail(StringBuffer buffer, String fieldName, Object value) { if (value.getClass().getName().startsWith("java.lang.") || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) { buffer.append(value); } else { depth++; buffer.append(ReflectionToStringBuilder.toString(value, this)); depth--; } } // another helpful method @Override protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) { depth++; buffer.append(ReflectionToStringBuilder.toString(coll.toArray(), this, true, true)); depth--; } }
Here is a modified version of @dma_k's solution featuring single buffer reuse, thread safety, multi-line indent and use of object's toString
method if it has been overridden.
Sample output:
ToStringTest.ParentStub { array = {a,b,c} map = {key2=null, key1=value1} child = ToStringTest.Stub { field1 = 12345 field2 = Hello superField = abc } empty = <null> superField = abc }
Code:
class RecursiveToStringStyle extends ToStringStyle { private static final RecursiveToStringStyle INSTANCE = new RecursiveToStringStyle(13); public static ToStringStyle getInstance() { return INSTANCE; } public static String toString(Object value) { final StringBuffer sb = new StringBuffer(512); INSTANCE.appendDetail(sb, null, value); return sb.toString(); } private final int maxDepth; private final String tabs; // http://stackoverflow.com/a/16934373/603516 private ThreadLocal<MutableInteger> depth = new ThreadLocal<MutableInteger>() { @Override protected MutableInteger initialValue() { return new MutableInteger(0); } }; protected RecursiveToStringStyle(int maxDepth) { this.maxDepth = maxDepth; tabs = StringUtils.repeat("\t", maxDepth); setUseShortClassName(true); setUseIdentityHashCode(false); setContentStart(" {"); setFieldSeparator(SystemUtils.LINE_SEPARATOR); setFieldSeparatorAtStart(true); setFieldNameValueSeparator(" = "); setContentEnd("}"); } private int getDepth() { return depth.get().get(); } private void padDepth(StringBuffer buffer) { buffer.append(tabs, 0, getDepth()); } private StringBuffer appendTabified(StringBuffer buffer, String value) { //return buffer.append(String.valueOf(value).replace("\n", "\n" + tabs.substring(0, getDepth()))); Matcher matcher = Pattern.compile("\n").matcher(value); String replacement = "\n" + tabs.substring(0, getDepth()); while (matcher.find()) { matcher.appendReplacement(buffer, replacement); } matcher.appendTail(buffer); return buffer; } @Override protected void appendFieldSeparator(StringBuffer buffer) { buffer.append(getFieldSeparator()); padDepth(buffer); } @Override public void appendStart(StringBuffer buffer, Object object) { depth.get().increment(); super.appendStart(buffer, object); } @Override public void appendEnd(StringBuffer buffer, Object object) { super.appendEnd(buffer, object); buffer.setLength(buffer.length() - getContentEnd().length()); buffer.append(SystemUtils.LINE_SEPARATOR); depth.get().decrement(); padDepth(buffer); appendContentEnd(buffer); } @Override protected void removeLastFieldSeparator(StringBuffer buffer) { int len = buffer.length(); int sepLen = getFieldSeparator().length() + getDepth(); if (len > 0 && sepLen > 0 && len >= sepLen) { buffer.setLength(len - sepLen); } } private boolean noReflectionNeeded(Object value) { try { return value != null && (value.getClass().getName().startsWith("java.lang.") || value.getClass().getMethod("toString").getDeclaringClass() != Object.class); } catch (NoSuchMethodException e) { throw new IllegalStateException(e); } } @Override protected void appendDetail(StringBuffer buffer, String fieldName, Object value) { if (getDepth() >= maxDepth || noReflectionNeeded(value)) { appendTabified(buffer, String.valueOf(value)); } else { new ReflectionToStringBuilder(value, this, buffer, null, false, false).toString(); } } // another helpful method, for collections: @Override protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) { buffer.append(ReflectionToStringBuilder.toString(coll.toArray(), this, true, true)); } static class MutableInteger { private int value; MutableInteger(int value) { this.value = value; } public final int get() { return value; } public final void increment() { ++value; } public final void decrement() { --value; } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With