46

我正在使用Jackson, with Spring MVC, 将一些简单的对象写成JSON. 其中一个对象具有amount类型的属性Double。(我知道不Double应该将其用作货币金额。但是,这不是我的代码。)

JSON输出中,我想将金额限制为小数点后 2 位。目前显示为:

"amount":459.99999999999994

我试过使用 Spring 3 的@NumberFormat注释,但在那个方向上没有成功。看起来其他人也有问题:MappingJacksonHttpMessageConverter's ObjectMapper does not use ConversionService when binding JSON to JavaBean propertiesenter link description here

此外,我尝试使用@JsonSerialize带有自定义序列化程序的注释。
在模型中:

@JsonSerialize(using = CustomDoubleSerializer.class)
public Double getAmount()

和序列化器实现:

public class CustomDoubleSerializer extends JsonSerializer<Double> {
    @Override
    public void serialize(Double value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
        if (null == value) {
            //write the word 'null' if there's no value available
            jgen.writeNull();
        } else {
            final String pattern = ".##";
            //final String pattern = "###,###,##0.00";
            final DecimalFormat myFormatter = new DecimalFormat(pattern);
            final String output = myFormatter.format(value);
            jgen.writeNumber(output);
        }
    }
}

CustomDoubleSerializer似乎”工作。但是,任何人都可以建议任何其他更简单(或更标准)的方式来做到这一点。

4

5 回答 5

8

我知道不Double应该将其用作货币金额。但是,这不是我的代码。

确实,不应该。BigDecimal是存储货币金额的更好选择,因为它是无损的 ,并且可以更好地控制小数位。

所以对于确实可以控制代码的人来说,它可以这样使用:

double amount = 111.222;
setAmount(new BigDecimal(amount).setScale(2, BigDecimal.ROUND_HALF_UP));

这将序列化为111.22. 不需要自定义序列化程序。

于 2017-09-27T12:53:42.907 回答
5

我的项目中有类似的情况。我已将格式化代码添加到 POJO 的 setter 方法中。DecimalFormatter、Math 和其他类最终将值四舍五入,但是,我的要求不是四舍五入,而只是将显示限制为小数点后 2 位。

我重新创建了这个场景。Product 是一个 POJO,它有一个 member Double amount。JavaToJSON 是一个将创建 Product 实例并将其转换为 JSON 的类。设置器将处理格式到小数点后 2 位setAmountProduct

这是完整的代码。

产品.java

package com;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Product {

    private String name;
    private Double amount;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Double getAmount() {
        return amount;
    }
    public void setAmount(Double amount) {
        BigDecimal bd = new BigDecimal(amount).setScale(2, RoundingMode.FLOOR);
        this.amount = bd.doubleValue();
    }

    @Override
    public String toString() {
        return "Product [name=" + name + ", amount=" + amount + "]";
    }
}

JavaToJSON.java

package com;

import java.io.File;
import java.io.IOException;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

public class JavaToJSON {

    public static void main(String[] args){

        ObjectMapper mapper = new ObjectMapper();

        try {
            Product product = new Product();
            product.setName("TestProduct");
            product.setAmount(Double.valueOf("459.99999999999994"));

            // Convert product to JSON and write to file
            mapper.writeValue(new File("d:\\user.json"), product);

            // display to console
            System.out.println(product);

        } catch (JsonGenerationException e) {

            e.printStackTrace();

        } catch (JsonMappingException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }
    }

}

我没有积累足够的积分,所以我无法上传截图给你看输出。

希望这可以帮助。

于 2015-08-06T22:31:14.923 回答
2

关于上面所说的,我只是想修复一些东西,这样人们就不会像我一样浪费时间了。一个应该实际使用

BigDecimal.valueOf(amount).xxx

代替

new BigDecimal(amount).xxx

这实际上有点关键。因为如果你不这样做,你的十进制金额就会被搞砸。这是浮点表示的限制,如此处所述

于 2018-06-01T14:03:00.970 回答
0

到目前为止我见过的最好的方法是创建一个自定义的序列化程序和@JsonSerializer(using=NewClass.class). 想尝试一下@JsonFormat(pattern=".##"),但根据 OP 的一条评论它可能不起作用(我认为格式化程序不尊重这一点)

见这里:https ://github.com/FasterXML/jackson-databind/issues/632

public class MoneyDeserializer extends JsonDeserializer<BigDecimal> {

    private NumberDeserializers.BigDecimalDeserializer delegate = NumberDeserializers.BigDecimalDeserializer.instance;

    @Override
    public BigDecimal deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        BigDecimal bd = delegate.deserialize(jp, ctxt);
        bd = bd.setScale(2, RoundingMode.HALF_UP);
        return bd;
    }    
}

但是,虽然更方便且编写的代码更少,但通常,决定字段的规模是业务逻辑的关注点,而不是(反)序列化的一部分。要清楚这一点。杰克逊应该能够按原样传递数据。

于 2020-06-16T13:51:51.510 回答
-4

请注意,459.99999999999994 实际上是 460,并且预计会以这种方式进行序列化。所以,你的逻辑应该比仅仅丢数字更棘手。我可能会提出类似的建议:

Math.round(value*10)/10.0

您可能希望将其放入 setter 中,并摆脱自定义序列化。

于 2014-03-21T19:07:17.790 回答