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

时间:2014-11-03 23:38:09

标签: java jackson json-deserialization decimalformat formatexception

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

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

由于某种原因,服务器使用逗号作为生成InvalidFormatException的小数分隔符。由于我无法更改服务器输出格式,因此我想教杰克逊ObjectMapper处理这些案件。以下是相关代码:

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

这是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这些坐标值是否带有德语区域设置?


我认为a custom deserializer for the specific fields as discussed here将是最佳选择。我起草了这个:

public class GermanFloatDeserializer extends JsonDeserializer<Float> {

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

}

3 个答案:

答案 0 :(得分:6)

我提出了以下解决方案:

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;

}

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

  

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

答案 1 :(得分:2)

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

以下实现(受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,无法解析。

答案 2 :(得分:0)

尽管已经接受了答案,但有一种方法可以摆脱那些@JsonDeserialize注释。

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

按照教程from official web-site,您只需执行以下操作:

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

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

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

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

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