Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
MichaelRFairhurst opened this issue Dec 3, 2015 · 2 comments
Milestone

Comments

@MichaelRFairhurst
Copy link

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.
@cowtowncoder
Copy link
Member

Yes, I can reproduce this with 2.7.0-rc1, still.

@cowtowncoder
Copy link
Member

Actually just realized that the problem was not with value type (which works as expected), but with key type. Current any-setter functionality assumes String usage, so this is missing functionality.

So I will consider this an RFE, and I hope it can be implemented relatively soon. But it is not a trivially simple fix.

cowtowncoder added a commit that referenced this issue Dec 7, 2015
@cowtowncoder cowtowncoder changed the title Adding properties to a HashMap using JsonAnySetter; no longer getting key deserialization @JsonAnySetter assumes key of String, does not consider declared type; should Oct 5, 2016
@cowtowncoder cowtowncoder modified the milestones: 2.2, 2.9.0 Oct 6, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants