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 <[email protected]> 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 <[email protected]> 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 [email protected]. >>> > To post to this group, send email to [email protected]. >>> > 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 [email protected]. > To post to this group, send email to [email protected]. > 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 [email protected]. To post to this group, send email to [email protected]. For more options, visit https://groups.google.com/d/optout.
