The issue itself: https://github.com/FasterXML/jackson-databind/issues/2236
has now been fixed for upcoming 2.10.0 -+ Tatu +- On Thu, Feb 7, 2019 at 8:10 PM Tatu Saloranta <t...@fasterxml.com> wrote: > Ok. As to the original question: reading and writing floating-point > values is pretty slow, in general, so I suspect your approach is not > overly inefficient. > (there was a recent post by someone pointing to some > higher-performance new implementation, which might be interesting -- > but still, binary floating point to/from 10-based textual > representation is one of areas where textual formats are at > disadvantage). > > -+ Tatu +- > > On Sun, Jan 27, 2019 at 12:29 PM C-B-B <c.bea...@gmail.com> wrote: > > > > After much trial and error I've been able to implement a workaround for > including the type on NaN and infinity Doubles during the serialization of > the desired Map objects, without getting in the general code path for > Double serialization. > > It's a little hacky (in particular I'm not retrieving the TypeResolver > completely dynamically - advice welcome there) but it might keep me going > until the Jackson lib supports this, and it should help in case anyone has > the same use case. > > Please let me know if you have better ideas! > > > > > > import com.fasterxml.jackson.annotation.JsonTypeInfo; > > import com.fasterxml.jackson.core.JsonGenerator; > > import com.fasterxml.jackson.databind.*; > > import com.fasterxml.jackson.databind.annotation.JsonSerialize; > > import com.fasterxml.jackson.databind.jsontype.TypeSerializer; > > import com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver; > > import > com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder; > > import com.fasterxml.jackson.databind.ser.std.MapSerializer; > > import com.fasterxml.jackson.databind.ser.std.NumberSerializers; > > import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; > > import com.fasterxml.jackson.databind.type.SimpleType; > > > > import java.io.IOException; > > import java.util.Collections; > > import java.util.HashMap; > > import java.util.Map; > > import java.util.Set; > > > > import static > com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap.emptyForProperties; > > > > public class Repro { > > private static ObjectMapper objectMapper = new ObjectMapper(); > > > > public static void main(String[] args) { > > try { > > String beanString = objectMapper.writeValueAsString(new > Bean(1L, Double.NaN)); > > System.out.println(beanString); > > MapHolder beanOut = objectMapper.readValue(beanString, > MapHolder.class); > > System.out.println(beanOut.data.get("double").getClass()); > > beanString = objectMapper.writeValueAsString(new Bean(1L, > 1D)); > > System.out.println(beanString); > > beanOut = objectMapper.readValue(beanString, > MapHolder.class); > > System.out.println(beanOut.data.get("double").getClass()); > > } catch (IOException e) { > > e.printStackTrace(); > > } > > } > > > > public static class Bean { > > private Long longValue; > > private Double doubleValue; > > > > public Bean(Long longValue, Double doubleValue) { > > this.longValue = longValue; > > this.doubleValue = doubleValue; > > } > > > > @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) > > @JsonSerialize(using = CustomMapSerializer.class) > > public Map<String, Object> getData() { > > Map<String, Object> map = new HashMap<>(); > > map.put("long", longValue); > > map.put("double", doubleValue); > > return map; > > } > > } > > > > public static class MapHolder { > > @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) > > public Map<String, Object> data; > > > > MapHolder() { > > } > > } > > > > public static class CustomDoubleSerializer extends > JsonSerializer<Object> { > > private NumberSerializers.DoubleSerializer doubleSerializer; > > private StdScalarSerializer<Object> scalarSerializer; > > > > public CustomDoubleSerializer() { > > this.doubleSerializer = new > NumberSerializers.DoubleSerializer(Double.class); > > this.scalarSerializer = new > StdScalarSerializer<Object>(Object.class) { > > @Override > > public void serialize(Object aDouble, JsonGenerator > jsonGenerator, SerializerProvider serializerProvider) throws IOException { > > doubleSerializer.serialize(aDouble, jsonGenerator, > serializerProvider); > > } > > }; > > } > > > > @Override > > public void serialize(Object aDouble, JsonGenerator > jsonGenerator, SerializerProvider serializerProvider) throws IOException { > > doubleSerializer.serialize(aDouble, jsonGenerator, > serializerProvider); > > } > > > > @Override > > public void serializeWithType(Object aDouble, JsonGenerator > jsonGenerator, SerializerProvider serializerProvider, TypeSerializer > typeSerializer) throws IOException { > > if (aDouble instanceof Double && (((Double) > aDouble).isInfinite() || ((Double) aDouble).isNaN())) { > > scalarSerializer.serializeWithType(aDouble, > jsonGenerator, serializerProvider, typeSerializer); > > } else { > > doubleSerializer.serialize(aDouble, jsonGenerator, > serializerProvider); > > } > > } > > } > > > > > > public static class CustomMapSerializer extends MapSerializer { > > /* Should be used for the initial instantiation, but overwritten > by createContextual */ > > CustomMapSerializer() { > > super(Collections.emptySet(), > SimpleType.constructUnsafe(String.class), > SimpleType.constructUnsafe(Object.class), > > false, null, null, null); > > } > > > > CustomMapSerializer(MapSerializer src) { > > this(src, null, false); > > this._dynamicValueSerializers = > emptyForProperties().addSerializer(Double.class, new > CustomDoubleSerializer()).map; > > } > > > > CustomMapSerializer(MapSerializer src, Object filterId, boolean > sortKeys) { > > super(src, filterId, sortKeys); > > this._dynamicValueSerializers = > emptyForProperties().addSerializer(Double.class, new > CustomDoubleSerializer()).map; > > } > > > > CustomMapSerializer(MapSerializer src, TypeSerializer vts, > Object suppressableValue, boolean suppressNulls) { > > super(src, vts, suppressableValue, suppressNulls); > > this._dynamicValueSerializers = > emptyForProperties().addSerializer(Double.class, new > CustomDoubleSerializer()).map; > > } > > > > CustomMapSerializer(MapSerializer src, BeanProperty property, > JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer, > Set<String> ignoredEntries) { > > super(src, property, keySerializer, valueSerializer, > ignoredEntries); > > this._dynamicValueSerializers = > emptyForProperties().addSerializer(Double.class, new > CustomDoubleSerializer()).map; > > } > > > > @Override > > public CustomMapSerializer withResolved(BeanProperty property, > JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer, > Set<String> ignored, boolean sortKeys) { > > CustomMapSerializer ser = new CustomMapSerializer(this, > property, keySerializer, valueSerializer, ignored); > > if (sortKeys != ser._sortKeys) { > > ser = new CustomMapSerializer(ser, this._filterId, > sortKeys); > > } > > return ser; > > } > > > > @Override > > public CustomMapSerializer > _withValueTypeSerializer(TypeSerializer vts) { > > if (this._valueTypeSerializer == vts) { > > return this; > > } else { > > return new CustomMapSerializer(this, vts, > this._suppressableValue, this._suppressNulls); > > } > > } > > > > @Override > > public JsonSerializer<?> createContextual(SerializerProvider > provider, BeanProperty property) throws JsonMappingException { > > JsonSerializer ser = provider.findValueSerializer(Map.class); > > if (ser instanceof MapSerializer) { > > MapSerializer mapSer = (MapSerializer) ((MapSerializer) > ser).createContextual(provider, property); > > JsonTypeInfo typeInfo = > property.getAnnotation(JsonTypeInfo.class); > > // Would be good to find a way to retrieve the > TypeSerializer dynamically... > > TypeSerializer typeSer = new > StdTypeResolverBuilder().init(typeInfo.use(), new > ClassNameIdResolver(SimpleType.constructUnsafe(Object.class), > provider.getTypeFactory())) > > > .inclusion(typeInfo.include()).buildTypeSerializer(provider.getConfig(), > SimpleType.constructUnsafe(Object.class), Collections.emptyList()); > > CustomMapSerializer customMapSerializer = new > CustomMapSerializer(mapSer); > > return > customMapSerializer._withValueTypeSerializer(typeSer); > > } > > return this; > > } > > } > > } > > > > > > On Sunday, 27 January 2019 10:27:16 UTC, C-B-B wrote: > >> > >> Many thanks for your response Tatu - looking forward to a fix. > >> > >> A couple more questions on the approach for the workaround: > >> > >> I've implemented the below CustomDoubleSerializer - how bad do you > think the performance impact would be for our Double serialisations, at a > high level, between "barely noticeable" and "very bad"? > >> Isn't there a way I can get this to only apply for Map element > serialisations? > >> Or even better, create a custom MapSerializer that would behave just > like the defaut MapSerializer, except when it comes to serializing Doubles? > If so what's the easiest way to do so? I'm getting a little lost when > trying to do that! > >> > >> Many thanks, > >> CBB > >> > >> PS : apologies for the dark code background, not sure how best to > include code snippets > >> > >> import com.fasterxml.jackson.core.JsonGenerator; > >> import com.fasterxml.jackson.databind.JsonSerializer; > >> import com.fasterxml.jackson.databind.SerializerProvider; > >> import com.fasterxml.jackson.databind.jsontype.TypeSerializer; > >> import com.fasterxml.jackson.databind.ser.std.NumberSerializers; > >> import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; > >> > >> import java.io.IOException; > >> > >> public class CustomDoubleSerializer extends JsonSerializer<Double> { > >> private NumberSerializers.DoubleSerializer doubleSerializer; > >> private StdScalarSerializer<Double> scalarSerializer; > >> > >> public CustomDoubleSerializer() { > >> this.doubleSerializer = new > NumberSerializers.DoubleSerializer(Double.class); > >> this.scalarSerializer = new > StdScalarSerializer<Double>(Double.class) { > >> @Override > >> public void serialize(Double aDouble, JsonGenerator > jsonGenerator, SerializerProvider serializerProvider) throws IOException { > >> doubleSerializer.serialize(aDouble, jsonGenerator, > serializerProvider); > >> } > >> }; > >> } > >> > >> @Override > >> public void serialize(Double aDouble, JsonGenerator jsonGenerator, > SerializerProvider serializerProvider) throws IOException { > >> doubleSerializer.serialize(aDouble, jsonGenerator, > serializerProvider); > >> } > >> > >> @Override > >> public void serializeWithType(Double aDouble, JsonGenerator > jsonGenerator, SerializerProvider serializerProvider, TypeSerializer > typeSerializer) throws IOException { > >> if (aDouble != null && (aDouble.isInfinite() || > aDouble.isNaN())) { > >> scalarSerializer.serializeWithType(aDouble, jsonGenerator, > serializerProvider, typeSerializer); > >> } else { > >> doubleSerializer.serialize(aDouble, jsonGenerator, > serializerProvider); > >> } > >> } > >> } > >> > >> > >> > >> > >> > >> > >> On Sunday, 27 January 2019 07:22:07 UTC, Tatu Saloranta wrote: > >>> > >>> On Sat, Jan 26, 2019 at 7:08 PM C-B-B <c.be...@gmail.com> wrote: > >>> > > >>> > Hello folks, > >>> > > >>> > I've been trying to serialise and deserialise a Map<String, Object> > whilst preserving the type of the elements, for example a Long should > remain a Long and not become an Integer. > >>> > This works pretty well using the @JsonTypeInfo annotation on the > Map. Jackson is being clever, and only including the type info when it is > needed (e.g. it won't include it on an Integer, String, Double etc as they > are default deserialisation types anyway), which is nice. > >>> > There's however a nasty edge case with Double.NaN, > Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY: they get serialised > without type info, and get deserialised as Strings... > >>> > > >>> > I've raised this as an issue, but in the meantime I'm looking for a > workaround to include the type info on these Doubles (I've checked, and the > deserialisation of NaN and infinity values works fine if the type is > included). > >>> > Ideally, the customised code would only be invoked for that Map > object, rather that for all serialisations (I've been able to override the > DoubleSerializer altogether on the ObjectMapper, but would rather not make > such an invasive change). > >>> > >>> I agree that this is a bug, and hope to eventually resolve it as per > >>> issue filed. > >>> > >>> But in the meantime I do think that custom deserializer is the way to > >>> go; and there's no easy way to change to only occur for Map values > >>> (delegation model means that same DoubleDeserializer is used for > >>> properties and map values). > >>> > >>> -+ Tatu +- > >>> > >>> > > >>> > I've tried a few things, including with TypeIdResolver, but no luck > so far... Any help would be much appreciated! > >>> > > >>> > Here's a little repro > >>> > > >>> > import com.fasterxml.jackson.annotation.JsonTypeInfo; > >>> > import com.fasterxml.jackson.databind.ObjectMapper; > >>> > import java.io.IOException; > >>> > import java.util.HashMap; > >>> > import java.util.Map; > >>> > > >>> > public class Repro { > >>> > private static ObjectMapper objectMapper = new ObjectMapper(); > >>> > > >>> > public static void main(String[] args) { > >>> > try { > >>> > String beanString = objectMapper.writeValueAsString(new > Bean(1L, Double.NaN)); > >>> > MapHolder beanOut = objectMapper.readValue(beanString, > MapHolder.class); > >>> > > System.out.println(beanOut.data.get("double").getClass()); > >>> > beanString = objectMapper.writeValueAsString(new > Bean(1L, 1D)); > >>> > beanOut = objectMapper.readValue(beanString, > MapHolder.class); > >>> > > System.out.println(beanOut.data.get("double").getClass()); > >>> > } catch (IOException e) { > >>> > e.printStackTrace(); > >>> > } > >>> > } > >>> > > >>> > public static class Bean { > >>> > private Long longValue; > >>> > private Double doubleValue; > >>> > > >>> > public Bean(Long longValue, Double doubleValue) { > >>> > this.longValue = longValue; > >>> > this.doubleValue = doubleValue; > >>> > } > >>> > > >>> > @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = > JsonTypeInfo.As.PROPERTY, property = "@class") > >>> > public Map<String, Object> getData() { > >>> > Map<String, Object> map = new HashMap<>(); > >>> > map.put("long", longValue); > >>> > map.put("double", doubleValue); > >>> > return map; > >>> > } > >>> > } > >>> > > >>> > public static class MapHolder { > >>> > public Map<String, Object> data; > >>> > MapHolder() {} > >>> > } > >>> > } > >>> > > >>> > > >>> > > >>> > -- > >>> > You received this message because you are subscribed to the Google > Groups "jackson-user" group. > >>> > To unsubscribe from this group and stop receiving emails from it, > send an email to jackson-user...@googlegroups.com. > >>> > To post to this group, send email to jackso...@googlegroups.com. > >>> > For more options, visit https://groups.google.com/d/optout. > > > > -- > > You received this message because you are subscribed to the Google > Groups "jackson-user" group. > > To unsubscribe from this group and stop receiving emails from it, send > an email to jackson-user+unsubscr...@googlegroups.com. > > To post to this group, send email to jackson-user@googlegroups.com. > > For more options, visit https://groups.google.com/d/optout. > > -- > You received this message because you are subscribed to the Google Groups > "jackson-user" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to jackson-user+unsubscr...@googlegroups.com. > To post to this group, send email to jackson-user@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "jackson-user" group. To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user+unsubscr...@googlegroups.com. To post to this group, send email to jackson-user@googlegroups.com. For more options, visit https://groups.google.com/d/optout.