jackson-databind
jackson-databind copied to clipboard
Inconsistent behavior for Double.NaN as map value in case of a standalone map and object property
Describe the bug Writing and subsequent reading of Double.NaN as map value succeeds for a standalone Map, but fails if the map is an object property. A user in the jackson-user-group suggested to file a bug report.
Version information 2.13.0
To Reproduce
Here are the Unit-Tests
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
class JacksonNaNMapValueExample
{
private static class SimpleClass
{
private Map<String, Double> map;
public SimpleClass() {}
public SimpleClass setMap(Map<String, Double> map) {this.map = map;return this;}
public Map<String, Double> getMap() {return map;}
@Override public String toString()
{
return "SimpleClass{" + "map=" + map + '}';
}
}
private static ObjectMapper mapper;
@BeforeAll
static void setup(){
mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT). //
setSerializationInclusion(JsonInclude.Include.NON_NULL). //
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS). //
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false). //
setVisibility(
mapper.getSerializationConfig() //
.getDefaultVisibilityChecker() //
.withFieldVisibility(JsonAutoDetect.Visibility.ANY) //
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)); //
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build();
mapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.EVERYTHING);
}
@Test
void testMapWithDoubleNaNStandalone()
{
Map<String, Double> map = new HashMap<>();
map.put("A", Double.NaN);
map.put("B", Double.POSITIVE_INFINITY);
Map<String, Double> restoredMap = new HashMap<>();
Assertions.assertDoesNotThrow(() -> {
String jsonString = mapper.writeValueAsString(map);
restoredMap.putAll(mapper.readValue(jsonString, Map.class));
});
Assertions.assertEquals(Double.NaN,restoredMap.get("A"));
Assertions.assertEquals(Double.POSITIVE_INFINITY,restoredMap.get("B"));
}
@Test
void testMapWithDoubleNaNAsObjectProperty()
{
Map<String, Double> map = new HashMap<>();
map.put("A", Double.NaN);
map.put("B", Double.POSITIVE_INFINITY);
SimpleClass obj = new SimpleClass();
obj.setMap(map);
Map<String, Double> restoredMap = new HashMap<>();
Assertions.assertDoesNotThrow(() -> {
String jsonString = mapper.writeValueAsString(obj);
restoredMap.putAll( mapper.readValue(jsonString, SimpleClass.class).getMap());
});
Assertions.assertEquals(Double.NaN,restoredMap.get("A"));
Assertions.assertEquals(Double.POSITIVE_INFINITY,restoredMap.get("B"));
}
}
Expected behavior
Both Unit-Tests should succeed or fail. If jackson is working as expected, I kindly request an explanation :)
Additional context
If
ObjectMapper.DefaultTyping.EVERYTHING
is replaced with
ObjectMapper.DefaultTyping.NON_FINAL
both tests succeed.
First things first: one thing I try to emphasize every chance I get is to please do not use generic Map
s or Collection
s (or even POJOs) as the Root Value -- ever. This tends to lead to issues with Java Type Erasure which is what happens here: because JDK only offers type information for root Map
as Map<?, ?>
, polymorphic type information tends not to work as well. But when said Map
is a POJO property, type declaration (Map<String, Double>
) is available from class definition.
Whether this is what causes specific issue here I don't know; I'll see if I can figure this out when I have time to look into it. But it is something to consider in general.
EVERYTHING: it tends to add Type Ids everywhere,even in cases where type can not be anything other than declared,Double 's declared value type is final, it results in adding type information in many places where it should not be needed,so,an error occurred
@JavaCodeWking I recommend against ever using EVERYTHING
for anything because it does what it says -- adds type id (almost) everywhere. I wish I had not added this choice in the first place, but it was requested by multiple users.
But it does what it says so only use it if you really know you want it, for some reason.