java web项目中BigDecimal返回值的序列化问题解决方案

在java web项目中返回一个费用,费用的类型使用了BigDecimal,然后就直接按照公司框架封装了返回值,结果就报错了。
项目使用的框架是SpringBoot + swagger + lombok,代码如下:
请求接口:

@ApiOperation(value = "查询费用测试2")
@PostMapping(path = "/getFee2/{fee}")
public ResultVo getFee2(@PathVariable String fee) {

    BigDecimal f = StringUtils.isEmpty(fee) ? null : new BigDecimal(fee);

    return ResultVo.ok(f);
}

返回结果的封装类

@ApiModel("返回信息包装类")
@Data
public class ResultVo {
    public static final int OK_CODE = 0;
    public static final int ERROR_CODE = 1;
    private static final String DEFAULT_MESSAGE = "OK";
    private static final ResultVo OK_VO;
    
    private String code;
    private String errorMsg;
    private T content;

    static {
        OK_VO = new ResultVo<>(OK_CODE, DEFAULT_MESSAGE);
    }

    public ResultVo(String code, String errorMsg, T content) {
        this.code = code;
        this.errorMsg = errorMsg;
        this.content = content;
    }

    public static  ResultVo ok(T content) {
        return new ResultVo<>(OK_CODE, null, content);
    }

    public static  ResultVo error(T content) {
        return new ResultVo<>(ERROR_CODE, null, content);
    }

    public static ResultVo ok() {
        return OK_VO;
    }
    
    public ResultVo() {
    
    }
}

接口请求链接:

curl -X POST "http://localhost:8080/shop/getFee2/10" -H "accept: */*"

请求的返回结果是:
image.png
错误的大概意思就是BigDecimal类型的数据在序列化时出了问题。

解决方案一:

在yml中添加如下配置

spring:
  jackson:
    serialization:
      FAIL_ON_EMPTY_BEANS: false

或者在代码中添加

@Bean
    public ObjectMapper objectMapper(){
        return new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
    }

结果就是请求接口不再报错,但是返回的数据的值变成了null。这个结果不符合我的预期。

解决方案二:

把BigDecimal类型的数据转换成其他的数据类型返回,比如说String,代码如下:

@ApiOperation(value = "查询费用测试2")
@PostMapping(path = "/getFee2/{fee}")
public ResultVo getFee2(@PathVariable String fee) {

    BigDecimal f = StringUtils.isEmpty(fee) ? null : new BigDecimal(fee);

    return ResultVo.ok(Objects.nonNull(f) ? f.toString() : null);
}

返回结果如下:

{
  "code": "0",
  "errorMsg": null,
  "content": "10.00"
}

看起来是解决了问题,但是没有办法自定义数据格式,比如说如果数字是整数的话,我并不想要小数点以及小数点后面的数字。

解决方案三:

添加BigDecimal的序列化方法,添加内容如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = BigDecimalSerializer.class)
@JsonDeserialize(using = BigDecimalDeSerializer.class)
public @interface BigDecimalFormat {

    String value() default "#.000";
}
@JsonComponent
public class BigDecimalDeSerializer extends JsonDeserializer {

    @Override
    public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {

        return new BigDecimal(jsonParser.getText());
    }
}
@JsonComponent
public class BigDecimalSerializer extends JsonSerializer implements ContextualSerializer {


    // 默认格式化方案, 项目中添加了 BigDecimal 的格式化配置后,
    private String format = "#.00";

    @Override
    public JsonSerializer createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {

        if(beanProperty !=null ){

            if(Objects.equals(beanProperty.getType().getRawClass(),BigDecimal.class)){

                BigDecimalFormat bigDecimalFormat = beanProperty.getAnnotation((BigDecimalFormat.class));
                if(bigDecimalFormat == null){

                    bigDecimalFormat = beanProperty.getContextAnnotation(BigDecimalFormat.class);
                }
                BigDecimalSerializer bigDecimalSerializer = new BigDecimalSerializer();
                if(bigDecimalFormat != null){

                    bigDecimalSerializer.format = bigDecimalFormat.value();
                }
                return bigDecimalSerializer;
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(),beanProperty);
        }
        return serializerProvider.findNullValueSerializer(beanProperty);
    }

    @Override
    public void serialize(BigDecimal bigDecimal, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {

        jsonGenerator.writeString(new DecimalFormat(format).format(bigDecimal));
    }
}

使用方式如下:
把返回结果费用放到对象中,在费用字段上添加序列化注解,代码如下:

@Data
public class GetFeeRspVo {
    @ApiModelProperty(name = "费用")
    @BigDecimalFormat("#.##")
    private BigDecimal fee;

    public GetFeeRspVo() {

    }

    public GetFeeRspVo(BigDecimal fee) {
        this.fee = fee;
    }
}

请求接口返回结果如下:

{
  "code": "0",
  "errorMsg": null,
  "content": {
    "fee": "10"
  }
}

看起来是返回结果满足了我们的需要

解决方案四:

能否不包装类呢?研究了方案三的序列化方式,找了核心的序列化代码

jsonGenerator.writeString(new DecimalFormat(format).format(bigDecimal));

根据这行代码我们可以自己对BigDecimal数据手动序列化,完整代码如下:

@ApiOperation(value = "查询费用测试1")
@PostMapping(path = "/getFee1/{fee}")
public ResultVo getFee1(@PathVariable String fee) {

    BigDecimal f = StringUtils.isEmpty(fee) ? null : new BigDecimal(fee);

    return ResultVo.ok(new DecimalFormat("#.##").format(f));
}

返回结果如下:

{
  "code": "0",
  "errorMsg": null,
  "content": "10"
}

看起来也能满足要求。

总结:方案三、方案四都是能够满足要求的。其中方案三更通用一些,方案四代码更简洁一些。

参考文章:
https://blog.csdn.net/read225...
https://blog.csdn.net/qq_4135...

你可能感兴趣的:(java web项目中BigDecimal返回值的序列化问题解决方案)