That's great news, thanks Tatu!

On Sunday, 10 February 2019 18:34:22 UTC, Tatu Saloranta wrote:
>
> 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 <[email protected] 
> <javascript:>> 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 <[email protected] <javascript:>> 
>> 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] <javascript:>.
>> > To post to this group, send email to [email protected] 
>> <javascript:>.
>> > 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] <javascript:>.
>> To post to this group, send email to [email protected] 
>> <javascript:>.
>> 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.

Reply via email to