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.

Reply via email to