简体   繁体   中英

Serialise BigDecimal value with customised scale per class field

I have a JSON seriliazer which serializes a BigDecimal in the way presented in this SO answer :

public class MoneySerializer extends JsonSerializer<BigDecimal> {
    @Override
    public void serialize(BigDecimal bigDecimal, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(bigDecimal.toPlainString());
    }
}

It is used in my POJO :

public class MoneyBean {
    //...

    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal amount1;

    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal amount2;
  
    // getters/setters
}

Requirements:

  1. I have to remove all trailing zeros but keep two from amount1 eg: if result is 100.0000 - intended result is 100.00
  2. For amount2 I have to remove all trailing zeros eg: if result is 100.0000 - intended result is 100

Is there any way the MoneySerializer can be adjusted to pass another argument as minimumScale so that it can remove automatically? Obviously this logic can be moved to service or we can create different serializer for each case but here I am looking for a single annotation which solves the problem something like:

@MyJsonSerialize(using = MoneySerializer.class, minimumScale = 2)
private BigDecimal amount1;  //100.00000 -> 100.00

@MyJsonSerialize(using = MoneySerializer.class, minimumScale = 0)
private BigDecimal amount2; //100.00000 -> 100

@MyJsonSerialize(using = MoneySerializer.class, minimumScale = 4)
private BigDecimal amount3;   //100.00000 -> 100.0000

Tried with different serializer or moving trailing zeros removal logic to service class and that is working but looking for a configurable annotation based solution.

I did some research and thought about the code for you before I went to bed. Try what I wrote :)

add this annotation class

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAccuracy {
    int value() default 0;
}

and

public class Entity {

@MyAccuracy(2)
@JsonSerialize(using = MoneySerializer.class)
public BigDecimal amount1;


@MyAccuracy(0)
@JsonSerialize(using = MoneySerializer.class)
public BigDecimal amount2;

@MyAccuracy(4)
@JsonSerialize(using = MoneySerializer.class)
public BigDecimal amount3;


}

and

public class MoneySerializer extends JsonSerializer<BigDecimal> {



@Override
public void serialize(BigDecimal bigDecimal, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    String fieldName = jsonGenerator.getOutputContext().getCurrentName();
    Class css = jsonGenerator.getOutputContext().getCurrentValue().getClass();
    boolean edit = false;
    try{
        MyAccuracy myAccuracy = css.getField(fieldName).getAnnotation(MyAccuracy.class);
        jsonGenerator.writeString(bigDecimal.setScale(myAccuracy.value(),BigDecimal.ROUND_HALF_UP).toPlainString());
        edit = true;
    }catch (Exception e){
        e.printStackTrace();
    }
    if(!edit) {
        jsonGenerator.writeString(bigDecimal.toPlainString());
    }
}

}

finally

public static void main(String[] args) throws JsonProcessingException {
    Entity entity = new Entity();
    entity.amount1 = BigDecimal.valueOf(2.2222);
    entity.amount2 = BigDecimal.valueOf(3.3333);
    entity.amount3 = BigDecimal.valueOf(4.4444);

    ObjectMapper objectMapper = new ObjectMapper();
    String jsonString = objectMapper.writeValueAsString(entity);
    System.out.println(jsonString);
}

final result

{"amount1":"2.22","amount2":"3","amount3":"4.4444"}

Good luck~

When we want to customise serialiser per field we need to use com.fasterxml.jackson.databind.ser.ContextualSerializer to create customised version. It allows us to read annotation for a given field and we do that only once so during the serialisation we do not need to execute Reflection API . Also, we can reuse com.fasterxml.jackson.annotation.JsonFormat annotation to provide pattern for BigDecimal . Using JsonFormat we can also handle shape in case we want to print it as a number for some properties. Below you can find complete example:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import lombok.Data;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Objects;

public class BigDecimalApp {
    public static void main(String[] args) throws IOException {
        var mapper = JsonMapper.builder()
                .enable(SerializationFeature.INDENT_OUTPUT)
                .build();

        var entity = new Entity();
        entity.setAmount1(new BigDecimal("100.4567"));
        entity.setAmount2(new BigDecimal("100.4567"));
        entity.setAmount3(new BigDecimal("100.4567"));
        entity.setAmountAsNumber1(new BigDecimal("100.4567"));
        entity.setAmountAsNumber2(new BigDecimal("100.4567"));
        entity.setAmountAsNumber3(new BigDecimal("100.4567"));

        mapper.writeValue(System.out, entity);
    }
}

@Data
class Entity {

    @JsonFormat(pattern = "#")
    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal amount1;

    @JsonFormat(pattern = "#.00")
    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal amount2;

    @JsonFormat(pattern = "#.0000")
    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal amount3;

    @JsonFormat(shape = Shape.NUMBER_INT)
    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal amountAsNumber1;

    @JsonFormat(pattern = "#.00", shape = Shape.NUMBER)
    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal amountAsNumber2;

    @JsonFormat(pattern = "#.0000", shape = Shape.NUMBER)
    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal amountAsNumber3;
}

class MoneySerializer extends StdSerializer<BigDecimal> implements ContextualSerializer {

    private static final MoneySerializer INSTANCE = new MoneySerializer();

    private final NumberFormat formatter;
    private final Shape shape;

    public MoneySerializer() {
        this(DecimalFormat.getInstance(), Shape.STRING);
    }

    protected MoneySerializer(NumberFormat formatter, Shape shape) {
        super(BigDecimal.class);
        this.formatter = Objects.requireNonNull(formatter);
        this.shape = Objects.requireNonNull(shape);
    }

    @Override
    public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if (Objects.isNull(value)) {
            jsonGenerator.writeNull();
        } else {
            switch (shape) {
                case NUMBER, NUMBER_FLOAT, NATURAL -> jsonGenerator.writeNumber(value.setScale(formatter.getMaximumFractionDigits(), RoundingMode.HALF_UP));
                case NUMBER_INT -> jsonGenerator.writeNumber(value.intValue());
                default -> jsonGenerator.writeString(formatter.format(value));
            }
        }
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
        JsonFormat jsonFormat = property.getAnnotation(JsonFormat.class);
        if (Objects.isNull(jsonFormat)) {
            return INSTANCE;
        }

        return new MoneySerializer(new DecimalFormat(jsonFormat.pattern()), jsonFormat.shape());
    }
}

Above code prints:

{
  "amount1" : "100",
  "amount2" : "100.46",
  "amount3" : "100.4567",
  "amountAsNumber1" : 100,
  "amountAsNumber2" : 100.46,
  "amountAsNumber3" : 100.4567
}

See also:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM