Java 到 Jackson JSON 序列化:Money 字段

Posted

技术标签:

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

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

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

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

【问题讨论】:

您将这些值存储在什么位置? BigDecimal? 【参考方案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 docs、Spring 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));
    

【讨论】:

此功能仅适用于 Jackson 版本 2.9.5 (github.com/FasterXML/jackson-databind/issues/1911) 我遇到了一个问题,这是我们代码使用的库中的分辨率,但我们的应用程序固定为旧版本的杰克逊。【参考方案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));
    

【讨论】:

以上是关于Java 到 Jackson JSON 序列化:Money 字段的主要内容,如果未能解决你的问题,请参考以下文章

jackson中objectMapper的使用

Java Jackson json 到对象反序列化。如何处理 OWASP 不安全的反序列化?

Java下利用Jackson进行JSON解析和序列化

Java下利用Jackson进行JSON解析和序列化1

Java下利用Jackson进行JSON解析和序列化

Java下用Jackson进行JSON序列化和反序列化(转)