Spring+Hibernate: How to convert entities to XML/JSON?

Spring makes it remarkably easy to write controllers which return XML or JSON response. Simply add the annotation @ResponseBody to your controller definition and you are done. Whatever you will return from your controller method will be automatically converted to XML or JSON (depending upon “Accept” header of the request). The return type of the controller can be any Object: ranging from maps and beans to collections and arrays.

However, if the return type of the controller is an entity bean or a list of them, you may face a problem: while attempting to convert an entity, Spring will attempt to access all of it’s properties including one-to-many and many-to-many collections, which may load your entire database into memory despite your lazy loading settings; hence your application will either go out of memory or you will get some other exception. So the correct way of converting your entities into JSON/XML is to make sure that you don’t convert one-to-many and many-to-many collections. But how would you stop Spring from converting them?

One way is to employ facade pattern. You can wrap your entity into a facade and expose only those properties which you want to convert. But if you have too many entities in your domain model or if your domain model changes frequently, facade pattern can become a nightmare.

Another (better) approach is to change the return type of your controller to a Map (or a collection of Maps if it was originally returning a collection of entities). So in your controller, you will first need to instantiate a Map for your entity, then add each of the eligible properties one by one to the map. But this approach has it’s limitations as well: if your entity has too many properties or the names of the properties change often, then keeping up with the new changes will become a problem.

However, these issues can be easily overcome with the help of Java Reflection API. You can write a static utility function toMap(Object o) which takes any object and converts it into a map. Here’s what I wrote for myself:

public static Map<String, Object> toMap(Object o, Set<String> excludedProperties) {
    Map<String, Object> map = new HashMap<String, Object>();
    Class<?> clazz = o.getClass();
    Method[] methods = clazz.getDeclaredMethods();
    for (Method m : methods) {
        String name = m.getName();
        boolean startsWithGet = name.startsWith("get");
        boolean startsWithIs = name.startsWith("is");
        if (( startsWithGet|| startsWithIs) && m.getTypeParameters().length==0) {
            String key = name.replaceFirst(startsWithGet ? "get" : "is", "");
            key = key.substring(0, 1).toLowerCase()+key.substring(1);
            if (excludedProperties.contains(key)) {
                continue;
            }
            Object value = null;
            try {
                value = m.invoke(o);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            map.put(key, value);
        }
    }
    return map;
}

public static Map<String, Object> toMap(Object o, String ... excludedProperties) {
    Set<String> set = new HashSet<String>();
    for (String s : excludedProperties) {
        set.add(s);
    }
    return toMap(o, set);
}

public static Collection<Map<String, Object>> toMap(Collection<?> c, String ... excludedProperties) {
    Set<String> expSet = new HashSet<String>();
    for (String s : excludedProperties) {
        expSet.add(s);
    }
    Collection<Map<String, Object>> result = new ArrayList<Map<String,Object>>();
    for (Object o : c) {
        result.add(toMap(o, expSet));
    }
    return result;
}

 


Comments and suggestions are welcome.

Published by

Umar Ashfaq

Umar Ashfaq is a full-stack web developer. His core strength is building neat UIs with JavaScript on web but he also enjoys server side Java, NodeJS and Objective C. Follow him on twitter @umarashfaq87

One thought on “Spring+Hibernate: How to convert entities to XML/JSON?”

Leave a Reply

Your email address will not be published. Required fields are marked *