Converting Objects to Map and Back
Join the DZone community and get the full member experience.
In large enterprise applications, sometimes, we need to convert data objects to and from Map . Usually, it is an intermediate step to a special serialization. If it is possible to use something standard, then it is better to use that, but many times, the architecture envisioned by some lead architect, the rigid environment, or some similar reason does not make it possible to use JOOQ, Hibernate, Jackson, JAX, or something similar. In such a situation, as it happened to me a few years ago, we have to convert the objects to some proprietary format being string or binary, and the first step towards that direction is to convert the object to a Map .
Eventually, the conversion is more complex than just:
Because these objects are almost never maps on their own, what we really need in the conversion is to have a Map where each entry corresponds to a field in the MyObject class. The key in the entry is the name of the field, and the value is the actual value of the field possibly converted to a Map itself.
One solution is to use reflection and reflectively read the fields of the object and create the map from it. The other approach is to create a toMap() method in the class that needs to be converted to a Map that simply adds each field to the returned map using the name of the field. This is somewhat faster than the reflection-based solution, and the code is much simpler.
When I was facing this problem in a real application a few years ago, I was so frustrated writing the primitive but numerous toMap() methods for each data object that I created a simple reflection-based tool that to do it just for any class we wanted. Did it solve the problem? No.
This was a professional environment where not only the functionality matters but also the quality of the code and the quality of my code, judged by my fellow programmers, was not matching. They argued that the reflection-based solution is complex and in case it becomes part of the code base then the later joining average developers will not be able to maintain it. Well, I had to admit that they were correct. In a different situation, I would have said that the developer has to learn reflection and programming in Java on a level that is needed by the code. In this case, however, we were not speaking about a specific person, but rather somebody who comes and joins the team in the future, possibly sometime when we have already left the project. This person was assumed to be an average developer, which seemed to be reasonable as we did not know anything about this person. In that sense, the quality of the code was not good, because it was too complex. The quorum of the developer team decided that maintaining the numerous manually crafted toMap() method was going to be cheaper than finding senior and experienced developers in the future.
To be honest, I was a bit reluctant to accept their decision, but I accepted it even though I had the possibility to overrule it based simply on my position in the team. I tend to accept the decisions of the team even if I do not agree with that, but only if I can live with those decisions. If a decision is dangerous, terrible, and threatens the future of the project, then we have to keep discussing the details until we get to an agreement.
Years later, I started to create Java::Geci as a side project that you can download from http://github.com/verhas/javageci.
Java::Geci is a code generation tool that runs during the test phase of the Java development life cycle. Code generation in Java::Geci is a “test.” It runs the code generation, and in case all the generated code stays put, then the test was successful. In case anything in the code base changed in a way that causes the code generator to generate different code than before, and thus the source code changes, then the test fails. When a test fails, you have to fix the bug and run the build, including tests, again. In this case, the test generates the new, by now fixed, code, therefore, all you have to do is only to run the build again.
When developing the framework, I created some simple generators to generate equals() and hashCode() , setters and getters, a delegator generator, and finally, I could not resist but I created a general purpose toMap() generator. This generator generates code that converts the object to Map just as we discussed before and also the fromMap() that I did not mention before, but fairly obviously also needed.
Java::Geci generators are classes that implement the Generator interface. The Mapper generator does that extending the abstract class AbstractJavaGenerator . This lets the generator throw any exception easing the life of the generator developer, and also it already looks up the Java class, which was generated from the currently processed source. The generator has access to the actual Class object via the parameter klass and the same time to the source code via the parameter source , which represents the source code and provides methods to create Java code to be inserted into it.
The third parameter global is something like a map holding the configuration parameters that the source code annotation @Geci defines.
package javax0.geci.mapper; import . public class Mapper extends AbstractJavaGenerator < . @Override public void process(Source source, Classklass, CompoundParams global) throws Exception < final var gid = global.get("id"); var segment = source.open(gid); generateToMap(source, klass, global); generateFromMap(source, klass, global); final var factory = global.get("factory", "new >()"); final var placeHolders = Map.of( "mnemonic", mnemonic(), "generatedBy", generatedAnnotation.getCanonicalName(), "class", klass.getSimpleName(), "factory", factory, "Map", "java.util.Map", "HashMap", "java.util.HashMap" ); final var rawContent = segment.getContent(); try < segment.setContent(Format.format(rawContent, placeHolders)); >catch (BadSyntax badSyntax) < throw new IOException(badSyntax); >>
The generator itself only calls the two methods generateToMap() and generateFromMap() , which generate, as the names imply the toMap() and fromMap() methods into the class.
Both methods use the source generating support provided by the Segment class, and they also use the templating provided by Jamal. It is also important to note that the fields are collected calling the reflection tools method getAllFieldsSorted() , which returns all the field the class has in a definitive order that does not depend on the actual JVM vendor or version.
private void generateToMap(Source source, Class klass, CompoundParams global) throws Exception < final var fields = GeciReflectionTools.getAllFieldsSorted(klass); final var gid = global.get("id"); var segment = source.open(gid); segment.write_r(getResourceString("tomap.jam")); for (final var field : fields) < final var local = GeciReflectionTools.getParameters(field, mnemonic()); final var params = new CompoundParams(local, global); final var filter = params.get("filter", DEFAULTS); if (Selector.compile(filter).match(field)) < final var name = field.getName(); if (hasToMap(field.getType())) < segment.write("map.put(\"%s\", %s == null ? null : %s.toMap0(cache));", field2MapKey(name), name, name); >else < segment.write("map.put(\"%s\",%s);", field2MapKey(name), name); >> > segment.write("return map;") ._l(">\n\n"); >
The code selects only the fields that are denoted by the filter expression.
Convert an Object to Map in Java
Learn to convert an Object to a Java Map using different ways provided by Jackson and Gson APIs. It is also possible to use create your own solution using Java reflection, but it is not recommended to reinvent the wheel until the provided solutions do not serve your purpose.
In this tutorial, we will convert an instance of the following Employee class into a Map. The Employee class has simple types such as String, and new Java types such as LocalDate and Collection types.
We have a List of Role types to further demonstrate the behavior of different solutions. We will see how different solutions convert the nested types in the converted Map.
class Employee < private Integer id; private String name; private LocalDate dateOfBirth; private Listlocations; private List roles; > class Role
Jackson is a multi-purpose library that supports different types of conversions such as JSON or XML very well. Jackson also supports converting an Object to Map using the following ways:
2.1. Using ObjectMapper.convertValue()
The convertValue() method does two-step conversion from a given value into an instance of the given value type. It first serializes the given value into JSON and then binds JSON data into the value of the given type. But the conversion is more efficient since full serialization does not (need to) occur.
Note that in the following example we are registering the JavaTimeModule class because Jackson does not support new Java 8 Date-time classes, by default.
Employee employee = new Employee(1, "Alex", LocalDate.of(1995, 1, 2), List.of("Delhi", "Nevada"), List.of(new Role(11, "Finance"), new Role(12, "HR"))); System.out.println(convertObjectToMapUsingObjectMapper(employee)); //The conversion method static Map convertObjectToMapUsingObjectMapper(Employee employee)
Note that this method converts the associated and nested classes (such as Role) also into LinkedHashMap.
2.2. Using JavaPropsMapper to Convert to Properties
Another interesting solution is to convert the Object to Properties. Properties have a flat structure. Even the nested structures and collections convert into flat structure and all fields/values are converted into String. It may be a good solution in some cases.
Employee employee = new Employee(1, "Alex", LocalDate.of(1995, 1, 2), List.of("Delhi", "Nevada"), List.of(new Role(11, "Finance"), new Role(12, "HR"))); System.out.println(convertObjectToMapUsingJavaPropsMapper(employee)); //The conversion method static Properties convertObjectToMapUsingJavaPropsMapper(Employee employee) throws IOException
Let us check out the object hierarchy in the debugger.
If our project is already having Gson dependency in the application, we can consider using Gson.fromJson() to convert object to JSON and the converting the JSON to HashMap in the second step.
This technique uses full serialization and deserialization, so it may not give a better performance than Jackson. Use Gson when using Jackson is not possible for some reason.
Employee employee = new Employee(1, "Alex", LocalDate.of(1995, 1, 2), List.of("Delhi", "Nevada"), List.of(new Role(11, "Finance"), new Role(12, "HR"))); System.out.println(convertObjectToMapUsingGson(employee)); static Map convertObjectToMapUsingGson(Employee employee) < Gson gson = new GsonBuilder() .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) .create(); return gson.fromJson(gson.toJson(employee), new TypeToken>() < >.getType() ); >
There are primarily two differences between conversion using Gson and Jackson.
- Gson, by default, converts all number values to Double type.
- Gson uses LinkedTreeMap for nested classes in place of LinkedHashMap used by Jackson.
Another possible method, though not recommended, is reflection. Using reflection, we must write all the logic by ourselves, and thus there are chances of errors. If you adopt this approach, make sure to test it well before deploying it into production.
The following is a very basic snippet for a simple POJO class that relies on getting the value of a field by its name. We can tweak the method to fetch the value by its getter if there is such a requirement.
public static Map toKeyValuePairs(Object instance) < return Arrays.stream(Employee.class.getDeclaredFields()) .collect(Collectors.toMap( Field::getName, field -> < try < Object result = null; field.setAccessible(true); result = field.get(instance); return result != null ? result : ""; >catch (Exception e) < return ""; >>)); >
We can verify the map structure in the next diagram. It has the nested classes untouched because we are not accessing them or processing them in our logic. If you need to process them in your application then further expand the logic in toKeyValuePairs() method.
This Java tutorial taught us to convert a given Java object into a Map using different solutions. By looking at all solutions, Jackson seems the best solution in terms of ease and performance and is recommended approach in most cases.
For creating flat structures, we can convert Object to Properties using JavaPropsMapper.
Using Gson is quite similar to Jackson, but it does not provide any additional benefit, rather all numbers are converted to Double which may not a desirable situation in some cases. So use it when you cannot use Jackson.
Finally, reflection gives total control into your hand, and you must write every possible usecase/condition yourself.
Java – Convert Object to Map
This tutorial provides several ways of converting a Java object to a Map in Java.
1. Reflection
The traditional way of converting a Java object to a Map is through using the reflection mechanism provided by the JDK.
Suppose we have a class called Student that holds 2 fields id and name. The following method converts the Student object to a Map using reflection:
private void convertObjToMapReflection() < MapstudentMap = new HashMap(); Student student = new Student(); student.setId(1); student.setName("Terek"); Field[] allFields = student.getClass().getDeclaredFields(); for (Field field : allFields) < field.setAccessible(true); Object value = field.get(student); studentMap.put(field.getName(), value); >System.out.println(studentMap); >
2. Jackson
The other way of doing the conversion is through using Jackson library.
The following example uses the same example above for converting a Student object to Map using Jackson library.
private void convertObjToMapJackson() < ObjectMapper oMapper = new ObjectMapper(); Student student = new Student(); student.setId(1); student.setName("Terek"); MapstudentMap = oMapper.convertValue(student, Map.class); System.out.println(studentMap); >
Summary
This tutorial provides several ways of converting a Java object to a Map in Java.
Next Steps
If you’re interested in learning more about the basics of Java, coding, and software development, check out our Coding Essentials Guidebook for Developers, where we cover the essential languages, concepts, and tools that you’ll need to become a professional developer.
Thanks and happy coding! We hope you enjoyed this article. If you have any questions or comments, feel free to reach out to jacob@initialcommit.io.