Skip to content

@JsonAnySetter assumes key of String, does not consider declared type; should #1035

@MichaelRFairhurst

Description

@MichaelRFairhurst

I previously was deserializing the following fields via JSON

private Map<CityStateCountrySplitter, Metrics> cityMetrics;
private Map<Integer, Metric> dmaCodeMetrics;
private Map<String, Metric> interestMetrics;

But suddenly the Maps we were getting from this third party included some new fields, where both the keys and the values differed from what we were getting before.

{"dmaCodeMetrics":{123:{...},456:{...},...,"raw_count":55},...}

We always get these new values, and want to keep them, so I moved over to use a new object that defines that new field plus @JsonAnySetter. Essentially I'm trying to create an object that acts like a HashMap<K, V> but has some known fields that I want to deserialize specially.

// old class
private Breakdown<CityStateCountrySplitter> cityMetrics;
private Breakdown<Integer> dmaCodeMetrics;
private Breakdown<String> interestMetrics;

// Breakdown.java
private int raw_count;
private Map<T, Metrics> metrics;

@JsonAnySetter
public void addBreakdownPoint(T key, Metrics value) {
    metrics.add(key, value);
}

At first everything seemed to work. @JsonAnySetter figures out to deserialize my value as a Metrics object, and deserialization doesn't throw any exceptions. But jackson always passes a String into 'key'. Thanks to erasure, it happily accepts the wrong data, until I go to use it where I get class cast exceptions.

Unit Test

  private static class MyGeneric<T> {

      private String staticallyMappedProperty;
      private Map<T, Integer> dynamicallyMappedProperties = new HashMap<T, Integer>();

      public String getStaticallyMappedProperty() {
          return staticallyMappedProperty;
      }

      @JsonAnySetter
      public void addDynamicallyMappedProperty(T key, int value) {
          dynamicallyMappedProperties.put(key, value);
      }

    public void setStaticallyMappedProperty(String staticallyMappedProperty) {
          this.staticallyMappedProperty = staticallyMappedProperty;
      }

      @JsonAnyGetter
      public Map<T, Integer> getDynamicallyMappedProperties() {
          return dynamicallyMappedProperties;
      }
  }

  private static class MyWrapper {
      private MyGeneric<String> myStringGeneric;
      private MyGeneric<Integer> myIntegerGeneric;

      public MyGeneric<String> getMyStringGeneric() {
          return myStringGeneric;
      }

    public void setMyStringGeneric(MyGeneric<String> myStringGeneric) {
          this.myStringGeneric = myStringGeneric;
      }

      public MyGeneric<Integer> getMyIntegerGeneric() {
          return myIntegerGeneric;
      }

    public void setMyIntegerGeneric(MyGeneric<Integer> myIntegerGeneric) {
          this.myIntegerGeneric = myIntegerGeneric;
      }
  }

  @Test
  public void test() throws JsonParseException, JsonMappingException, IOException {
      ObjectMapper mapper = new ObjectMapper();

      Map<String, Integer> stringGenericMap = new HashMap<String, Integer>();
      stringGenericMap.put("testStringKey", 5);
      Map<Integer, Integer> integerGenericMap = new HashMap<Integer, Integer>();
      integerGenericMap.put(111, 6);

      MyWrapper deserialized = mapper.readValue("{\"myStringGeneric\":{\"staticallyMappedProperty\":\"Test\",\"testStringKey\":5},\"myIntegerGeneric\":{\"staticallyMappedProperty\":\"Test2\",\"111\":6}}", MyWrapper.class);
      MyGeneric<String> stringGeneric = deserialized.getMyStringGeneric();
      MyGeneric<Integer> integerGeneric = deserialized.getMyIntegerGeneric();

      assertNotNull(stringGeneric);
      assertEquals(stringGeneric.getStaticallyMappedProperty(), "Test");
      for(Map.Entry<String, Integer> entry : stringGeneric.getDynamicallyMappedProperties().entrySet()) {
          assertTrue("A key in MyGeneric<String> is not an String.", entry.getKey() instanceof String);
          assertTrue("A value in MyGeneric<Integer> is not an Integer.", entry.getValue() instanceof Integer);
      }
      assertEquals(stringGeneric.getDynamicallyMappedProperties(), stringGenericMap);

      assertNotNull(integerGeneric);
      assertEquals(integerGeneric.getStaticallyMappedProperty(), "Test2");
      for(Map.Entry<Integer, Integer> entry : integerGeneric.getDynamicallyMappedProperties().entrySet()) {
          assertTrue("A key in MyGeneric<Integer> is not an Integer.", entry.getKey() instanceof Integer);
          assertTrue("A value in MyGeneric<Integer> is not an Integer.", entry.getValue() instanceof Integer);
      }
      assertEquals(integerGeneric.getDynamicallyMappedProperties(), integerGenericMap);
  }

This test fails with

java.lang.AssertionError: A key in MyGeneric<Integer> is not an Integer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions