7

If a class extends a Map and includes some extra fields. E.G a last modified date, Jackson seems to ignore any thing not contained as a property of the map.

I have a class that looks some thing like the following:

import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Objects;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Foo extends HashMap<String, String> {

   private OffsetDateTime lastModifiedTime;

   public Foo() {
      super();
      lastModifiedTime = OffsetDateTime.now();
   }

   public void setLastModifiedTime(OffsetDateTime newTime) {
      this.lastModifiedTime = newTime;
   }

   public OffsetDateTime getLastModifiedTime() {
      return this.lastModifiedTime;
   }

   public static void main(String[] args) throws IOException {
      Foo f = new Foo();
      f.put("hello", "world");

      ObjectMapper om = new ObjectMapper();
      om.findAndRegisterModules();

      String result = om.writeValueAsString(f);
      if(f.equals(om.readValue(result, Foo.class))) {
         System.out.println("Wooo");
      } else {
         System.out.println("Booo: " + result);
      }

   }

   @Override
   public boolean equals(Object obj) {
      if (this == obj) {
         return true;
      }
      if (!(obj instanceof Foo)) {
         return false;
      }
      if (!super.equals(obj)) {
         return false;
      }
      Foo foo = (Foo) obj;
      return Objects.equals(getLastModifiedTime(), foo.getLastModifiedTime());
   }

   @Override
   public int hashCode() {
      return Objects.hash(super.hashCode(), getLastModifiedTime());
   }

}

When this runs it outputs booo: {"hello":"world"} Which does not include the last modified date.

I've tried adding @JsonInclude annotations to the property and the getters but that doesn't cause it to include the extra field.

What is the correct way to make Jackson include this extra field?

Wil Selwood
  • 1,112
  • 2
  • 11
  • 19

3 Answers3

8

It's generally considered bad practice to extend HashMap. Instead, you should create a class that has a Map, and the additional field.

This will solve your problem, but will also have additional advantages:

  • you will be able to encapsulate the rules (like preventing some keys/values to be added, etc.)
  • you will be able to change the Map implementation if needed (LinkedHashMap, synchronized map, concurrent map)
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
7

By default your map will fall into MapSerializer. You could register a serializer modifier to add the custom property, but you would be better off nesting the map as a property of Foo and unwrapping it:

@JsonUnwrapped
private HashMap<String, String> fooMap;
Sam Berry
  • 7,394
  • 6
  • 40
  • 58
6

Instead of @JsonUnwrapped, you can use @JsonAnyGetter and @JsonAnySetter like this :

public class Foo {

   private OffsetDateTime lastModifiedTime;
   private HashMap<String, String> additionalProperties = new HashMap<String, String>();

   public Foo() {
      super();
      lastModifiedTime = OffsetDateTime.now();
   }

   public void setLastModifiedTime(OffsetDateTime newTime) {
      this.lastModifiedTime = newTime;
   }

   public OffsetDateTime getLastModifiedTime() {
      return this.lastModifiedTime;
   }

   @JsonAnyGetter
   public Map<String, String> any() {
      return this.additionalProperties;
   }

   @JsonAnySetter
   public void set(String name, String value) {
      this.additionalProperties.put(name, value);
   }
}
manuc66
  • 2,701
  • 29
  • 28