【问题标题】:Java to Jackson JSON serialization: Money fieldsJava 到 Jackson JSON 序列化:Money 字段
【发布时间】:2012-07-04 09:19:37
【问题描述】:

目前,我正在使用 Jackson 从基于 Spring 的 Web 应用程序发送 JSON 结果。

我遇到的问题是试图让所有货币字段以 2 位小数输出。我无法使用 setScale(2) 解决这个问题,因为像 25.50 这样的数字会被截断为 25.5 等

还有其他人处理过这个问题吗?我正在考虑使用自定义 Jackson 序列化程序制作 Money 类...您可以为字段变量制作自定义序列化程序吗?您可能可以...但即便如此,我怎样才能让我的客户序列化程序将数字添加为带 2 位小数的数字?

【问题讨论】:

  • 您将这些值存储在什么位置? BigDecimal?

标签: java serialization jackson


【解决方案1】:

您可以在资金字段中使用自定义序列化程序。这是一个使用 MoneyBean 的示例。 amount 字段使用 @JsonSerialize(using=...) 进行注释。

public class MoneyBean {
    //...

    @JsonProperty("amountOfMoney")
    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal amount;

    //getters/setters...
}

public class MoneySerializer extends JsonSerializer<BigDecimal> {
    @Override
    public void serialize(BigDecimal value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
            JsonProcessingException {
        // put your desired money style here
        jgen.writeString(value.setScale(2, BigDecimal.ROUND_HALF_UP).toString());
    }
}

就是这样。 BigDecimal 现在以正确的方式打印。我用一个简单的测试用例来展示它:

@Test
public void jsonSerializationTest() throws Exception {
     MoneyBean m = new MoneyBean();
     m.setAmount(new BigDecimal("20.3"));

     ObjectMapper mapper = new ObjectMapper();
     assertEquals("{\"amountOfMoney\":\"20.30\"}", mapper.writeValueAsString(m));
}

【讨论】:

  • 不错的方法,但它会将其打印为字符串——而不是 JSON 输出中的数字类型。
  • 从业务角度来看,这是一种糟糕的方法。金钱不应该在序列化时四舍五入。如果您想填充尾随零(与盲目设置比例不同),那么您必须在没有ROUND_HALF_UP 的情况下这样做。此外,不同的货币需要不同的尾随小数位数。
  • jro,如果在 Steve 的 serialize() 中使用 writeNumber() 而不是 writeString(),那么该字段将在 JSON 中显示为数字。
  • @PeterDavis 是和否。这真的取决于你序列化它的目的。在现实世界中,Money 只有两位小数,例如,如果您的 API 返回 12.4999990008212354,那么实际的现实世界值应该是 12.49 还是 12.50?在税收世界中,他们更喜欢您支付 12.50。
  • @MaksymBykovskyy 重点不是不应发生舍入,而是这些舍入规则是业务逻辑,不应在序列化时应用它们。它们需要在您进行序列化之前应用,否则您会混淆问题。
【解决方案2】:

您可以在BigDecimal 变量上使用@JsonFormat 注释和shape 作为STRING。参考如下:

 import com.fasterxml.jackson.annotation.JsonFormat;

  class YourObjectClass {

      @JsonFormat(shape=JsonFormat.Shape.STRING)
      private BigDecimal yourVariable;

 }

【讨论】:

  • 是的,你是对的。此选项自 Jackson 2.9.5 起可用:github.com/FasterXML/jackson/wiki/Jackson-Release-2.9.5
  • 它对我有用。喜欢这个答案。这是一种非常简单和干净的做事方式。
  • 令人惊讶的是它对我不起作用(Spring Boot 2.3.7):( 而接受的答案中的@JsonSerialize 可以。
【解决方案3】:

除了在每个成员或 getter 上设置 @JsonSerialize 之外,您还可以配置一个为特定类型使用自定义序列化程序的模块:

SimpleModule module = new SimpleModule();
module.addSerializer(BigInteger.class, new ToStringSerializer());
objectMapper.registerModule(module);

在上面的例子中,我使用 to string serializer 来序列化 BigIntegers(因为 javascript 不能处理这样的数值)。

【讨论】:

  • 在哪里定义客户序列化程序?这正是我想做的,但我不知道该把代码放在哪里。
  • 随心所欲,只需要实现 com.fasterxml.jackson.databind.JsonSerializer
  • 你能再具体一点吗?我还是不知道放哪里。
  • 我试过这个,但它只适用于你试图序列化的原始类。如果对象具有这种类型的属性,则不会使用此序列化程序对其进行序列化(我使用的是 Spring boot 1.3)
  • @DaveH: 如果你的SpringMvcConfiguration 扩展DelegatingWebMvcConfiguration,那么你可以覆盖extendMessageConverters() 方法,在转换器列表中找到MappingJackson2HttpMessageConverter,然后像这样注册SimpleModule:mappingJackson2HttpMessageConverter.getObjectMapper().registerModule(simpleModule)。这样你就不会覆盖现有的mappingJackson2HttpMessageConverter
【解决方案4】:

我是jackson-datatype-money 的维护者之一,所以请谨慎回答这个问题,因为我肯定有偏见。该模块应该满足您的需求并且它非常轻量级(没有额外的运行时依赖项)。此外,Jackson docsSpring docs 中也提到了它,甚至还有 some discussions 已经在讨论如何将其集成到杰克逊的官方生态系统中。

【讨论】:

    【解决方案5】:

    我遇到了同样的问题,我将其格式化为 JSON 作为字符串。可能有点小技巧,但很容易实现。

    private BigDecimal myValue = new BigDecimal("25.50");
    ...
    public String getMyValue() {
        return myValue.setScale(2, BigDecimal.ROUND_HALF_UP).toString();
    }
    

    【讨论】:

      【解决方案6】:

      正如Sahil Chhabra 建议的那样,您可以在变量上使用@JsonFormat 和适当的shape。 如果您想将其应用于 Dto's 中的每个 BigDecimal 字段,您可以覆盖给定类的默认格式。

      @Configuration
      public class JacksonObjectMapperConfiguration {
      
          @Autowired
          public void customize(ObjectMapper objectMapper) {
               objectMapper
                  .configOverride(BigDecimal.class).setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));
          }
      }
      

      【讨论】:

      【解决方案7】:

      Steve 的启发,并作为 Java 11 的更新。以下是我们如何进行 BigDecimal 重新格式化以避免科学记数法。

      public class PriceSerializer extends JsonSerializer<BigDecimal> {
          @Override
          public void serialize(BigDecimal value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              // Using writNumber and removing toString make sure the output is number but not String.
              jgen.writeNumber(value.setScale(2, RoundingMode.HALF_UP));
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-04
        • 2018-07-14
        • 1970-01-01
        • 2012-08-31
        • 1970-01-01
        • 2017-09-08
        • 2019-11-25
        • 2019-01-20
        相关资源
        最近更新 更多