如何使用Jackson的本地化小数分隔符反序列化浮点值

我用Jackson解析的输入流包含纬度和经度值,例如:

{ "name": "product 23", "latitude": "52,48264", "longitude": "13,31822" } 

由于某种原因,服务器使用逗号作为小数分隔符,产生InvalidFormatException 。 由于我无法更改服务器输出格式,因此我想教jackson的ObjectMapper来处理这些情况。 这是相关的代码:

 public static Object getProducts(final String inputStream) { ObjectMapper objectMapper = new ObjectMapper(); try { return objectMapper.readValue(inputStream, new TypeReference() {} ); } catch (UnrecognizedPropertyException e) { e.printStackTrace(); } catch (InvalidFormatException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (JsonParseException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } 

这是POJO:

 import com.fasterxml.jackson.annotation.JsonProperty; public class Product { @JsonProperty("name") public String name; @JsonProperty("latitude") public float latitude; @JsonProperty("longitude") public float longitude; } 

我如何告诉Jackson这些坐标值是否带有德语区域设置?


我想这里讨论的特定字段的自定义反序列化器将是要走的路。 我起草了这个:

 public class GermanFloatDeserializer extends JsonDeserializer { @Override public Float deserialize(JsonParser parser, DeserializationContext context) throws IOException { // TODO Do some comma magic return floatValue; } } 

然后POJO看起来像这样:

 import com.fasterxml.jackson.annotation.JsonProperty; public class Product { @JsonProperty("name") public String name; @JsonDeserialize(using = GermanFloatDeserializer.class, as = Float.class) @JsonProperty("latitude") public float latitude; @JsonDeserialize(using = GermanFloatDeserializer.class, as = Float.class) @JsonProperty("longitude") public float longitude; } 

我提出了以下解决方案:

 public class FlexibleFloatDeserializer extends JsonDeserializer { @Override public Float deserialize(JsonParser parser, DeserializationContext context) throws IOException { String floatString = parser.getText(); if (floatString.contains(",")) { floatString = floatString.replace(",", "."); } return Float.valueOf(floatString); } } 

 public class Product { @JsonProperty("name") public String name; @JsonDeserialize(using = FlexibleFloatDeserializer.class) @JsonProperty("latitude") public float latitude; @JsonDeserialize(using = FlexibleFloatDeserializer.class) @JsonProperty("longitude") public float longitude; } 

我仍然想知道为什么当我将返回值类指定为as = Float.class时它不起作用,可以在JsonDeserialize的文档中JsonDeserialize 。 它看起来好像我应该使用其中一个而不是两个。 无论如何,文档还声称当定义using =时将忽略as =

如果也使用using(),它具有优先权(因为它直接指定了反序列化器,而这只用于定位反序列化器),并且忽略此注释属性的值。

比其他建议的答案更通用的解决方案,需要为每种类型注册单独的反序列化器,是为ObjectMapper提供定制的DefaultDeserializationContext

以下实现(受DefaultDeserializationContext.Impl启发)对我DefaultDeserializationContext.Impl

 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); } } } 

现在使用所需的语言环境设置对象映射器:

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

如果使用Spring RestTemplate ,可以将其设置为使用objectMapper如下所示:

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

请注意,该值必须表示为JSON文档中的字符串(即{"number": "2,2"} ),因为例如{"number": 2,2}无效JSON且无法解析。

考虑到已接受的答案,有一种方法可以摆脱那些@JsonDeserialize注释。

您需要在ObjectMapper中注册自定义反序列化器。

按照官方网站上的教程,您可以执行以下操作:

  ObjectMapper mapper = new ObjectMapper(); SimpleModule testModule = new SimpleModule( "DoubleCustomDeserializer", new com.fasterxml.jackson.core.Version(1, 0, 0, null)) .addDeserializer(Double.class, new JsonDeserializer() { @Override public Double deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { String valueAsString = jp.getValueAsString(); if (StringUtils.isEmpty(valueAsString)) { return null; } return Double.parseDouble(valueAsString.replaceAll(",", "\\.")); } }); mapper.registerModule(testModule); 

如果您使用Spring Boot,则有一种更简单的方法。 只需在Configuration类中的某处定义Jackson2ObjectMapperBuilder bean:

 @Bean public Jackson2ObjectMapperBuilder jacksonBuilder() { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.deserializerByType(Double.class, new JsonDeserializer() { @Override public Double deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { String valueAsString = jp.getValueAsString(); if (StringUtils.isEmpty(valueAsString)) { return null; } return Double.parseDouble(valueAsString.replaceAll(",", "\\.")); } }); builder.applicationContext(applicationContext); return builder; } 

并将自定义HttpMessageConverter添加到WebMvcConfigurerAdapter消息转换器列表中:

  messageConverters.add(new MappingJackson2HttpMessageConverter(jacksonBuilder().build())); 
Interesting Posts