Common Pitfalls When Converting Java Beans to Maps and Solutions Using Dubbo
Converting Java Beans to Maps can cause type loss and incorrect property names due to JSON serialization and BeanMap implementations, but using Dubbo's PojoUtils.generalize method or custom reflection with caching can preserve types and correctly map property names, as demonstrated with code examples and analysis.
Background Some business scenarios require converting Java Beans to Map, which seems simple but actually contains many pitfalls.
Pitfall 1: Type loss during JSON deserialization Using JSON frameworks such as Fastjson, Gson or Jackson can change the original data types when a Bean is serialized to JSON and then parsed back to a Map. For example, a Long value smaller than Integer.MAX_VALUE becomes an Integer , Date becomes a Long , and Double may become a Decimal type.
import lombok.Data;
import java.util.Date;
@Data
public class MockObject extends MockParent {
private Integer aInteger;
private Long aLong;
private Double aDouble;
private Date aDate;
}
@Data
public class MockParent {
private Long parent;
}Maven dependency for Fastjson:
com.alibaba
fastjson
2.0.8Example conversion code:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import java.util.Date;
import java.util.Map;
public class JsonDemo {
public static void main(String[] args) {
MockObject mockObject = new MockObject();
mockObject.setAInteger(1);
mockObject.setALong(2L);
mockObject.setADate(new Date());
mockObject.setADouble(3.4D);
mockObject.setParent(3L);
String json = JSON.toJSONString(mockObject);
Map
map = JSON.parseObject(json, new TypeReference
>() {});
System.out.println(map);
}
}Result printed:
{"parent":3,"ADouble":3.4,"ALong":2,"AInteger":1,"ADate":1657299916477}
Pitfall 2: Property name errors with BeanMap Both commons-beanutils BeanMap and cglib BeanMap suffer from incorrect property name resolution because they rely on java.beans.Introspector which applies Introspector.decapitalize . This method lower‑cases the first character unless the first two characters are both uppercase, causing names like getALong to become aLong and URL to become uRL .
import org.apache.commons.beanutils.BeanMap;
import third.fastjson.MockObject;
import java.util.Date;
public class BeanUtilsDemo {
public static void main(String[] args) {
MockObject mockObject = new MockObject();
mockObject.setAInteger(1);
mockObject.setALong(2L);
mockObject.setADate(new Date());
mockObject.setADouble(3.4D);
mockObject.setParent(3L);
BeanMap beanMap = new BeanMap(mockObject);
System.out.println(beanMap);
}
} import net.sf.cglib.beans.BeanMap;
import third.fastjson.MockObject;
import java.util.Date;
public class BeanMapDemo {
public static void main(String[] args) {
MockObject mockObject = new MockObject();
mockObject.setAInteger(1);
mockObject.setALong(2L);
mockObject.setADate(new Date());
mockObject.setADouble(3.4D);
mockObject.setParent(3L);
BeanMap beanMap = BeanMap.create(mockObject);
System.out.println(beanMap);
}
}The underlying code of Introspector.decapitalize shows why names such as URL are incorrectly transformed to uRL .
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))) {
return name;
}
char[] chars = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}Solution Dubbo provides a utility class org.apache.dubbo.common.utils.PojoUtils whose generalize method converts a POJO to a Map while preserving the original types and correctly handling property names.
import org.apache.dubbo.common.utils.PojoUtils;
import third.fastjson.MockObject;
import java.util.Date;
public class DubboPojoDemo {
public static void main(String[] args) {
MockObject mockObject = new MockObject();
mockObject.setAInteger(1);
mockObject.setALong(2L);
mockObject.setADate(new Date());
mockObject.setADouble(3.4D);
mockObject.setParent(3L);
Object generalized = PojoUtils.generalize(mockObject);
System.out.println(generalized);
}
}Key part of the implementation (simplified):
public static Object generalize(Object pojo) {
return generalize(pojo, new IdentityHashMap());
}
private static Object generalize(Object pojo, Map
history) {
if (pojo == null) return null;
if (pojo instanceof Enum
) return ((Enum
) pojo).name();
if (pojo.getClass().isArray() && Enum.class.isAssignableFrom(pojo.getClass().getComponentType())) {
int len = Array.getLength(pojo);
String[] values = new String[len];
for (int i = 0; i < len; i++) {
values[i] = ((Enum
) Array.get(pojo, i)).name();
}
return values;
}
if (ReflectUtils.isPrimitives(pojo.getClass())) return pojo;
if (pojo instanceof Class) return ((Class) pojo).getName();
// handle collections, maps, arrays recursively …
Map
map = new HashMap<>();
for (Method method : pojo.getClass().getMethods()) {
if (ReflectUtils.isBeanPropertyReadMethod(method)) {
ReflectUtils.makeAccessible(method);
map.put(ReflectUtils.getPropertyNameFromBeanReadMethod(method), generalize(method.invoke(pojo), history));
}
}
for (Field field : pojo.getClass().getFields()) {
if (ReflectUtils.isPublicInstanceField(field)) {
Object value = field.get(pojo);
if (value != null) {
map.put(field.getName(), generalize(value, history));
}
}
}
return map;
}This utility first checks for null, enums, primitives, arrays, collections and maps, then iterates over bean getter methods and public fields, applying generalize recursively while caching already‑processed objects to avoid infinite loops.
Conclusion Converting Java Beans to Maps is fraught with type‑loss and property‑name issues when using generic JSON serializers or BeanMap utilities. Dubbo's PojoUtils.generalize or a custom reflection‑based approach with caching can reliably preserve data types and generate correct map keys, reducing debugging effort and preventing subtle bugs.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.