-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
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.