如何使用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()));