separador reglas punto por pone origen numeros miles matematicas los entre ejemplos diferencia decimales coma java jackson json-deserialization decimalformat formatexception

java - reglas - separador de miles y decimales



Cómo deserializar un valor flotante con un separador decimal localizado con Jackson (3)

Se me ocurrió la siguiente solución:

public class FlexibleFloatDeserializer extends JsonDeserializer<Float> { @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; }

Todavía me pregunto por qué no funciona cuando especifico la clase de valor devuelto como as = Float.class como se puede encontrar en la documentación de JsonDeserialize . Se lee como si se supone que debo usar uno u otro, pero no ambos. En cualquier caso, los documentos también afirman que as = se ignorará cuando se using =

si también se usa using (), tiene prioridad (ya que especificó el deserializador directamente, mientras que esto solo se usaría para ubicar el deserializador) y se ignora el valor de esta propiedad de anotación.

La secuencia de entrada que estoy analizando con Jackson contiene valores de latitud y longitud, como aquí:

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

Por alguna razón, el servidor usa comas como el separador decimal que produce una InvalidFormatException . Como no puedo cambiar el formato de salida del servidor, me gustaría enseñar a ObjectMapper de Jackson a manejar esos casos. Aquí está el código relevante:

public static Object getProducts(final String inputStream) { ObjectMapper objectMapper = new ObjectMapper(); try { return objectMapper.readValue(inputStream, new TypeReference<Product>() {} ); } 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; }

Y aquí está el 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; }

¿Cómo puedo decirle a Jackson que esos valores de coordenadas vienen con una configuración regional alemana?

Supongo que un deserializador personalizado para los campos específicos que se analizan aquí sería el camino a seguir. Redacté esto:

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

Entonces el POJO se vería así:

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


Con todo respeto a la respuesta aceptada, hay una manera de deshacerse de esas anotaciones @JsonDeserialize .

Debe registrar el deserializador personalizado en ObjectMapper.

Siguiendo el tutorial del sitio web oficial , simplemente haz algo como:

ObjectMapper mapper = new ObjectMapper(); SimpleModule testModule = new SimpleModule( "DoubleCustomDeserializer", new com.fasterxml.jackson.core.Version(1, 0, 0, null)) .addDeserializer(Double.class, new JsonDeserializer<Double>() { @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);

Si está utilizando Spring Boot, hay un método más simple. Simplemente defina el bean Jackson2ObjectMapperBuilder en algún lugar de su clase de configuración:

@Bean public Jackson2ObjectMapperBuilder jacksonBuilder() { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.deserializerByType(Double.class, new JsonDeserializer<Double>() { @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; }

y agregue el HttpMessageConverter personalizado a la lista de convertidores de mensajes WebMvcConfigurerAdapter :

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


Una solución más general que las otras respuestas propuestas, que requieren el registro de deserializadores individuales para cada tipo, es proporcionar un DefaultDeserializationContext personalizado para ObjectMapper .

La siguiente implementación (que está inspirada en DefaultDeserializationContext.Impl ) funcionó para mí:

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

Ahora configura tu mapeador de objetos con la configuración regional deseada:

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

Si usa Spring RestTemplate , puede configurarlo para usar objectMapper manera:

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

Tenga en cuenta que el valor debe representarse como una cadena en el documento JSON (es decir, {"number": "2,2"} ), ya que, por ejemplo, {"number": 2,2} no es JSON válido y no podrá analizarse.