If you're using Jackson to serialize a Map object as part of your development efforts, you might be a wee bit disappointed with the results.

Maybe you want something like this:

{
  "id" : "A17",
  "service" : "Army",
  "disabled" : true
}

But you're getting something like this:

{
  "id" : "A17",
  "supplementalFields" : {
    "service" : "Army",
    "disabled" : true
  }
}

If you think about it, though, that's exactly what you should get. After all, Jackson is serialziing a Map object that has its own property name.

But even though it's what you should get, that doesn't mean it's what you want.

In this guide, I'll show you how to achieve the desired results.

Use @JsonAnyGetter

Well the title kind of gave the game away, didn't it?

If you want to get the output you're looking for, annotate the Map's getter method with @JsonAnyGetter.

According to the docs, the @JsonAnyGetter ensures that "contents of the returned Map (type must be Map) are serialized as if they were actual properties of the bean that contains method/field with this annotations."

Okay, so the grammar isn't perfect. But neither is mine.

Anyhoo, you get the idea.

Also note: you should only use @JsonAnyGetter once per class. Don't put it on multiple methods.

Now let's take a look at this thing in action.

A Simple Employee Class

Here's a simple Employee class with just an id property and a Map object that keeps supplemental properties not relevant to all employees.

private static class Employee {
    
    private String id;
    
    private Map<String, Object> supplementalFields;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Map<String, Object> getSupplementalFields() {
        return supplementalFields;
    }

    public void setSupplementalFields(Map<String, Object> supplementalFields) {
        this.supplementalFields = supplementalFields;
    }
}

Nothing really complicated there. In fact, it's exactly what I described above.

Now let's use our friend Jackson to serialize that bad boy.

Map<String, Object> map = new HashMap<>();
map.put("disabled", true);
map.put("service", "Army");

Employee employee = new Employee();
employee.setId("A17");
employee.setSupplementalFields(map);

ObjectMapper mapper = new ObjectMapper();
System.err.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));

That code starts off by creating a new Map object. That object includes a couple of key/value pairs with data that pertains to the employee.

Next, the code instantiates an Employee object. Then it sets the only two properties available on that object.

Not that the supplementalFields property is set with the Map object.

And then the code uses good ol' ObjectMapper to spit out the serialized version of that Employee object in JSON format. The results look like this:

{
  "id" : "A17",
  "supplementalFields" : {
    "service" : "Army",
    "disabled" : true
  }
}

And, as we've seen, that's not what you're looking for.

Update the Employee Class

So here's what you do. Make one simple change to the Employee class.

...

    @JsonAnyGetter
    public Map<String, Object> getSupplementalFields() {
        return supplementalFields;
    }

...

Pay attention to that @JsonAnyGetter annotation just above the getSupplementalFields() method. That's going to make all the difference.

Now run that same code block above again and you'll see these results:

{
  "id" : "A17",
  "service" : "Army",
  "disabled" : true
}

Now that's what you're looking for!

So what's happening? @JsonAnyGetter serializes the keys in the Map object as though they're properties in the parent object.

It's effectively the same as if service and disabled were properties of Employee.

Just keep in mind: in some cases, you're better off treating the entry sets in  a Map object separately. Think about your data modeling before you go this route.

Wrapping It Up

There you go. Now you know how to use @JsonAnyGetter with Jackson to serialize your Map keys as though they were top-level properties in the parent class.

Now feel free to take what you've learned here and include it in your own Spring Boot applications.

Have fun!

Alexander Mils on Unsplash