A DataType Printer in Java

For better or for worse, modern "enterprise" Java applications are full of deeply-nested hierarchies of objects where one instance might contain a list of other objects which may themselves contain other objects, ad nauseam... debugging an unfamiliar application written in this (not very object oriented) style means familiarizing yourself with the details of this hierarchy. Yet the tools we have at our disposal for debugging aren't very robust at introspecting deeply nested objects like this. You can load the code in a debugger (assuming it's running and deployable somewhere debugger friendly) and poke around the structure, but while the debugger tools are good for picking out specific values, they don't really do a good job of giving you a holistic view of the object's contents. Of course, there's always good old System.out.println, but you can end up writing an awful lot of code to format an object in a way that you can really see it — JSON.stringify (or the Jackson equivalent, ObjectMapper.writeValueAsString) can get you part of the way there if you already have a JSON dependency, but even then the output is missing some useful detail.

I finally decided to bite the bullet and write a generic, Java-only data formatting routine specifically for this purpose. Of course, if you're going to do this in pure Java, you're going to have to lean heavily on introspection, so you want to be careful not to introduce this into production code, but it's relatively easy to add and remove quickly since it's just a static function. I'll start out using the JavaBeans introspector — as it turns out, it's simple to use, but limited (I'll address that below). The simplest case is a single object with nothing but what the JavaBeans specification calls "properties", shown in listing 1.

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.IntrospectionException;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class DataTypePrinter  {
  public static String printDataType(Object o) throws IntrospectionException,
                                                      IllegalAccessException,
                                                      InvocationTargetException {
    StringBuffer stringRep = new StringBuffer();

    if (o != null)  {
      BeanInfo objectDesc = Introspector.getBeanInfo(o.getClass());
      PropertyDescriptor[] properties = objectDesc.getPropertyDescriptors();

      for (PropertyDescriptor property : properties)  {
        Class propertyType = property.getPropertyType();

        if (!propertyType.equals(Class.class))  {
          Method getter = property.getReadMethod();
          Object value = getter.invoke(o, new Object[] {});
          stringRep.append(property.getName() + "(" + propertyType.getSimpleName() + ") = " + 
            value.toString() + "\n");
        }
      }
    } else  {
      stringRep.append("null");
    }

    return stringRep.toString();
  }
}

Listing 1: Data Type Printer for simple types

This handles the simple base case when the "data type" under consideration just has getters for scalar properties. I'm not trying to introspect private fields; with a bit of fiddling you could do that, but that's not helpful for what I'm trying to do here. Even in this simple code listing, there are a couple of interesting things worth pointing out: first, I have to check the type of the property and make sure it isn't Class, since the JavaBeans introspector will return a getter for the class, which I don't want to print out (in fact, when I make this recursive below, including it would cause an infinite loop). Also, I invoke propertyType.getSimpleName() to print out the unqualified class name of each property after it's property name — package names are usually clutter and I don't want them here.

Listing 1 only works when the class includes simple properties, not properties that have properties themselves. It's not hard to extend this to introspect properties recursively as shown in listing 2:

  public static String dataType(String prefix,  Object o)
  ...
    if (!propertyType.equals(Class.class))  {
      Method getter = property.getReadMethod();
      Object value = getter.invoke(o, new Object[] {});
      if (value != null) {
        if (propertyType.isPrimitive() || propertyType.equals(String.class))  {
          stringRep.append(prefix +  "." + property.getName() + "(" + 
            propertyType.getSimpleName() + ") = " + value.toString() + "\n");
      } else {
          stringRep.append(printDataType(prefix + "." + property.getName(), value));
        }
      } else  {
        stringRep.append(prefix + "." + property.getName() + "(" + 
          propertyType.getSimpleName() + ") = null\n");
      }
    }

Listing 2: Recursive Data Type Printer

Here I've added a null check along with a prefix on each call, so that nested structures can print out their containing element. The effect is already pretty useful, as shown in listing 3:

class Z {
  private int s = 12;

  public int getS() { return s; }
}

class Y {
  private int q = 9;
  private int r = 10;
  private Z z = new Z();

  public int getQ() { return q; }
  public int getR() { return r; }
  public Z getZ() { return z; }
}

class X {
  private int a = 1;
  private int b = 3;
  private String c = "abc";
  private Y y = new Y();

  public int getA() { return a; }
  public int getB() { return b; }
  public String getC() { return c; }
  public Y getY() { return y; }
}

...
X x = new X();
System.out.println(printDataType("x", x);

/*
x.a(int) = 1
x.b(int) = 3
x.c(String) = abc
x.y.q(int) = 9
x.y.r(int) = 10
x.y.z.s(int) = 12
*/

Listing 3: Recursive output sample

This is why I omitted the Class getter above — first because it's not helpful in the output and second because it causes listing 2 to enter an infinite loop since java.lang.Class has its own getClass getter...

So far, so good - but what about collection types? With a little bit of refactoring, I can include a nice output for Iterable components. Before printing a property, I check to see if it's an implementation of Iterable: if so, I iterate over the contents and invoke printDataType on each. This is fully recursive, so if the iterated contents contain objects (or other iterables!), they'll output correctly.

  for (PropertyDescriptor property : properties)  {
    Class propertyType = property.getPropertyType();
    if (!propertyType.equals(Class.class))  {
      Method getter = property.getReadMethod();
      if (o instanceof Iterable)  {
        Iterator<Object> it = ((Iterable) o).iterator();

        int i = 0;
        while (it.hasNext())  {
          stringRep.append(printDataType(prefix + "[" + i++ + "]", 
            it.next()));
        }
      } else  {
        Object value = getter.invoke(o, new Object[] {});

        if (value != null)  {

Listing 4: iterate over collections.

Note that I check for iterable on the parent object - this works for nested objects because the reader will have been invoked recursively, and has the nice side effect that I can pass a List object directly into the printDataType and it will output correctly. Listing 4 as presented only works for collections if the listed type is a class with properties, though; it fails if the iterable is a native type or, as is probably most common, a String. The problem here is that my top-level handler assumes that what it's passed is either an iterable or an introspectable Java object. To handle native collections and strings (and, while we're at it, Dates) correctly, I can introduce a third case at the top:

    StringBuffer stringRep = new StringBuffer();

    if (o != null)  {
      Class objectType = o.getClass();

      if (objectType.isPrimitive() ||
          objectType.equals(String.class) ||
          objectType.equals(Date.class))  {
        stringRep.append(prefix + " (" + objectType.getSimpleName() + 
          ") = " + o.toString() + "\n");
      } else if (o instanceof Iterable) {
        Iterator it = ((Iterable) o).iterator();

        int i = 0;
        while (it.hasNext())  {
          stringRep.append(printDataType(prefix + "[" + i++ + "]", it.next()));
        }
      } else  {
        BeanInfo objectDesc = Introspector.getBeanInfo(o.getClass());
        PropertyDescriptor[] properties = objectDesc.getPropertyDescriptors();

Listing 5: Support for native collection types

This works for most java collections, but not for native arrays. As you probably know, Java handles arrays somewhat differently than most everything else for efficiency reasons, but Java reflection includes an Array that allows you to access members of arrays by index. So I can add a fourth "base case" here and include native arrays:

  if (objectType.isPrimitive() ||
          objectType.equals(String.class) ||
          objectType.equals(Date.class)) {
    stringRep.append(prefix + " (" + objectType.getSimpleName() + ") = " + o.toString() + "\n");
  } else if (objectType.isArray()) {
    if (Array.getLength(o) == 0)    {
      stringRep.append(prefix + " = empty array\n");
    }
    if (objectType.getComponentType().isPrimitive() ||
        objectType.getComponentType().equals(String.class) ||
        objectType.getComponentType().equals(Date.class))   {
      for (int i = 0; i < Array.getLength(o); i++) {
        stringRep.append(prefix + "[" + i + "] = " + 
          Array.get(o, i).toString() + "\n");
      }
    } else {
      for (int i = 0; i < Array.getLength(o); i++) {
        stringRep.append(printDataType(prefix + "[" + i + "]", 
          Array.get(o, i)));
      }
    }
  } else if (o instanceof Iterable) {
    int i = 0;

Listing 6: Support for native array types

There's nothing really tricky here; I just have to use java.lang.reflect.Array to get the length of and access each member of the array instance.

While we're in here, we might as well go ahead and support Map types — they're not Iterables, but by iterating over their Key values we can make it look like they are:

    }
  } else if (o instanceof Map)    {
    Set<Object> keys = ((Map) o).keySet();
    if (keys.isEmpty()) {
      stringRep.append(prefix + " = empty map\n");
    }
    for (Object key : keys)   {
      stringRep.append(printDataType(prefix + "[" + key.toString() + "]", 
        ((Map) o).get(key)));
    }
  } else

Listing 7: Support for Map types

This is a complete, working, useful data type printer. However, one thing I found when I started actually trying to use this is that there are quite a few getters that don't turn up as JavaBean properties, though — especially Collection types. I suppose this makes sense, since collection types aren't really Java Beans, but that means that I have to "roll my own" introspector. As it turns out, that's not too difficult, although it's a bit more code:

 else {
   Method[] methods = objectType.getMethods();

   for (Method method : methods)   {
     if (method.getName().startsWith("get") &&
         method.getParameterCount() == 0)    {
       String propertyName = method.getName().substring(3,4).toLowerCase() + 
         method.getName().substring(4);
       Object value = method.invoke(o, new Object[] {} );
       // value.getClass().isPrimitive returns false, even if 
       // method.getReturnType().isPrimitive returns true
       if (method.getReturnType().isPrimitive())   {
         stringRep.append(prefix + "." + propertyName + " (" + 
           method.getReturnType().getSimpleName() + ") = " + value.toString() + "\n");
       } else {
         stringRep.append(printDataType(prefix + "." + propertyName, value));
       }
     } else if (method.getName().startsWith("is") &&
                method.getParameterCount() == 0 &&
                method.getReturnType().equals(boolean.class))   {
       String propertyName = method.getName().substring(2,3).toLowerCase() + 
         method.getName().substring(3);
       stringRep.append(prefix + "." + propertyName + " (boolean) = " +
         (method.invoke(o, new Object[] {}).equals(true) ? "true" : "false") + "\n");
     }
   }
 }

Listing 8: Introspector without JavaBeans

Here I do pretty much what (I presume) Introspector does and make some inferences about what each method does: if it starts with get or is and doesn't take any parameters, it's a property that I want to introspect. Listing 9 is the final data type printer which I've actually found to be particularly helpful in making sense of new, large codebases.

public static String printDataType(String prefix, Object o) {
  StringBuilder stringRep = new StringBuilder();

  try {
    if (o != null) {
      Class objectType = o.getClass();
      if (objectType.isPrimitive() ||
          objectType.equals(String.class) ||
          objectType.equals(Date.class)) {
        stringRep.append(prefix + " (" + objectType.getSimpleName() + 
          ") = " + o.toString() + "\n");
      } else if (objectType.isArray()) {
        if (Array.getLength(o) == 0)  {
          stringRep.append(prefix + " = empty array\n");
        }
        if (objectType.getComponentType().isPrimitive() ||
          objectType.getComponentType().equals(String.class) ||
            objectType.getComponentType().equals(Date.class))  {
          for (int i = 0; i < Array.getLength(o); i++) {
            stringRep.append(prefix + "[" + i + "] = " + 
              Array.get(o, i).toString() + "\n");
          }
        } else {
          for (int i = 0; i < Array.getLength(o); i++) {
            stringRep.append(printDataType(prefix + "[" + i + "]", 
              Array.get(o, i)));
          }
        }
      } else if (o instanceof Iterable) {
        int i = 0;
        Iterator it = ((Iterable) o).iterator();
        if (!it.hasNext()) {
          stringRep.append(prefix + " = empty set\n");
        }
        while (it.hasNext()) {
          stringRep.append(printDataType(prefix + "[" + i++ + "]", 
            it.next()));
        }
      } else if (o instanceof Map)  {
        Set<Object> keys = ((Map) o).keySet();
        if (keys.isEmpty()) {
          stringRep.append(prefix + " = empty map\n");
        }
        for (Object key : keys)  {
          stringRep.append(printDataType(prefix + "[" + key.toString() + "]", 
            ((Map) o).get(key)));
        }
      } else if (o instanceof Class) {
        // Do nothing otherwise the stack blows up
      } else {
        Method[] methods = objectType.getMethods();

        for (Method method : methods)  {
          if (method.getName().startsWith("get") &&
            method.getParameterCount() == 0)  {
            String propertyName = method.getName().substring(3,4).toLowerCase() + 
              method.getName().substring(4);
            Object value = method.invoke(o, new Object[] {} );
            // value.getClass().isPrimitive returns false, even if 
            // method.getReturnType().isPrimitive returns true
            if (method.getReturnType().isPrimitive())  {
              stringRep.append(prefix + "." + propertyName + " (" + 
                method.getReturnType().getSimpleName() + ") = " + 
                value.toString() + "\n");
            } else {
              stringRep.append(printDataType(prefix + "." + propertyName, value));
            }
          } else if (method.getName().startsWith("is") &&
                method.getParameterCount() == 0 &&
                method.getReturnType().equals(boolean.class))  {
            String propertyName = method.getName().substring(2,3).toLowerCase() + 
              method.getName().substring(3);
            stringRep.append(prefix + "." + propertyName + " (boolean) = " +
                (method.invoke(o, new Object[] {}).equals(true) ? "true" : "false") + "\n");
          }
        }
      }
    } else {
      return prefix + " = null\n";
    }
  } catch (Exception e)  {
    e.printStackTrace();
    return e.toString();
  }

  return stringRep.toString();
}

Listing 9: Full data type printer

I mentioned JSON at the beginning of this post - it's worth noting that it would be relatively simple to modify this to output syntactically correct JSON, or even allow the caller to select a representation.

Add a comment:

Completely off-topic or spam comments will be removed at the discretion of the moderator.

You may preserve formatting (e.g. a code sample) by indenting with four spaces preceding the formatted line(s)

Name: Name is required
Email (will not be displayed publicly):
Comment:
Comment is required
My Book

I'm the author of the book "Implementing SSL/TLS Using Cryptography and PKI". Like the title says, this is a from-the-ground-up examination of the SSL protocol that provides security, integrity and privacy to most application-level internet protocols, most notably HTTP. I include the source code to a complete working SSL implementation, including the most popular cryptographic algorithms (DES, 3DES, RC4, AES, RSA, DSA, Diffie-Hellman, HMAC, MD5, SHA-1, SHA-256, and ECC), and show how they all fit together to provide transport-layer security.

My Picture

Joshua Davies

Past Posts