How to deserialize a float value with a localized decimal separator with Jackson

前端 未结 3 1375
别那么骄傲
别那么骄傲 2020-12-16 22:16

The input stream I am parsing with Jackson contains latitude and longitude values such as here:

{
    \"name\": \"pr         


        
3条回答
  •  一生所求
    2020-12-16 23:03

    A more general solution than the other proposed answers, which require registering individual deserializers for each type, is to provide a customized DefaultDeserializationContext to ObjectMapper.

    The following implementation (which is inspired by DefaultDeserializationContext.Impl) worked for me:

    class LocalizedDeserializationContext extends DefaultDeserializationContext {
        private final NumberFormat format;
    
        public LocalizedDeserializationContext(Locale locale) {
            // Passing `BeanDeserializerFactory.instance` because this is what happens at
            // 'jackson-databind-2.8.1-sources.jar!/com/fasterxml/jackson/databind/ObjectMapper.java:562'.
            this(BeanDeserializerFactory.instance, DecimalFormat.getNumberInstance(locale));
        }
    
        private LocalizedDeserializationContext(DeserializerFactory factory, NumberFormat format) {
            super(factory, null);
            this.format = format;
        }
    
        private LocalizedDeserializationContext(DefaultDeserializationContext src, DeserializationConfig config, JsonParser parser, InjectableValues values, NumberFormat format) {
            super(src, config, parser, values);
            this.format = format;
        }
    
        @Override
        public DefaultDeserializationContext with(DeserializerFactory factory) {
            return new LocalizedDeserializationContext(factory, format);
        }
    
        @Override
        public DefaultDeserializationContext createInstance(DeserializationConfig config, JsonParser parser, InjectableValues values) {
            return new LocalizedDeserializationContext(this, config, parser, values, format);
        }
    
        @Override
        public Object handleWeirdStringValue(Class targetClass, String value, String msg, Object... msgArgs) throws IOException {
            // This method is called when default deserialization fails.
            if (targetClass == float.class || targetClass == Float.class) {
                return parseNumber(value).floatValue();
            }
            if (targetClass == double.class || targetClass == Double.class) {
                return parseNumber(value).doubleValue();
            }
            // TODO Handle `targetClass == BigDecimal.class`?
            return super.handleWeirdStringValue(targetClass, value, msg, msgArgs);
        }
    
        // Is synchronized because `NumberFormat` isn't thread-safe.
        private synchronized Number parseNumber(String value) throws IOException {
            try {
                return format.parse(value);
            } catch (ParseException e) {
                throw new IOException(e);
            }
        }
    }
    

    Now set up your object mapper with your desired locale:

    Locale locale = Locale.forLanguageTag("da-DK");
    ObjectMapper objectMapper = new ObjectMapper(null,
                                                 null,
                                                 new LocalizedDeserializationContext(locale));
    

    If you use Spring RestTemplate, you can set it up to use objectMapper like so:

    RestTemplate template = new RestTemplate();
    template.setMessageConverters(
        Collections.singletonList(new MappingJackson2HttpMessageConverter(objectMapper))
    );
    

    Note that the value must be represented as a string in the JSON document (i.e. {"number": "2,2"}), since e.g. {"number": 2,2} is not valid JSON and will fail to parse.

提交回复
热议问题