需要 Jackson 序列化器来实现 Double 并且需要在运行时指定精度

问题描述 投票:0回答:4

有很多关于为数字、货币等创建 Jackson 序列化器的文章。对于工程应用程序,通常需要根据单位或其他标准设置数字的精度。

例如,空间坐标可能限制为小数点后 5 或 6 位,温度可能限制为小数点后 2 位。具有太多数字或截断指数表示法的默认序列化器行为不好。我需要的是这样的:

@JsonSerialize(using=MyDoubleSerializer.class, precision=6) double myValue;

更好的是能够在运行时指定精度。我也在使用 MixIn。我可以为每个类编写一个序列化器,但希望指定特定值。

任何想法将不胜感激。

java json jackson serialization
4个回答
28
投票

您可以使用 Jackson 的

ContextualSerializer
来实现所需的序列化,如下所示。

首先,创建一个注释来捕获精度

@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Precision {
    int precision();
}

接下来,为

Double
类型创建一个上下文序列化器,该序列化器在要序列化的字段上查找
Precision
注释,然后为指定的精度创建一个新的序列化器。

public class DoubleContextualSerializer extends JsonSerializer<Double> implements ContextualSerializer {

    private int precision = 0;

    public DoubleContextualSerializer (int precision) {
        this.precision = precision;
    }

    public DoubleContextualSerializer () {

    }

    @Override
    public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
        if (precision == 0) {
            gen.writeNumber(value.doubleValue());
        } else {
            BigDecimal bd = new BigDecimal(value);
            bd = bd.setScale(precision, RoundingMode.HALF_UP);
            gen.writeNumber(bd.doubleValue());
        }

    }
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        Precision precision = property.getAnnotation(Precision.class);
        if (precision != null) {
            return new DoubleContextualSerializer (precision.precision());
        }
        return this;
    }
}

最后,注释您的字段以使用自定义序列化器并设置精度

public class Bean{

   @JsonSerialize(using = DoubleContextualSerializer .class)
   @Precision(precision = 2)
   private double doubleNumber;

}

希望这有帮助!


1
投票

我使用了大部分建议的代码,但执行了以下操作,它使用 DecimalFormat 进行格式化,这需要输出原始文本:

import java.io.IOException;
import java.text.DecimalFormat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;

/**
 * Custom serializer to serialize Double to a specified precision in output string.
 * The @FormatterPrecision(precision=2) annotation needs to have been specified, for example:
 * <pre>
 * @JsonSerialize(using=JacksonJsonDoubleSerializer.class) @FormatterPrecision(precision=6) abstract Double getLatitude();
 * </pre>
 * @author sam
 *
 */
public class JacksonJsonDoubleSerializer extends JsonSerializer<Double> implements ContextualSerializer {

    /**
     * Precision = number of digits after the decimal point to display.
     * Last digit will be rounded depending on the value of the next digit.
     */
    private int precision = 4;

    /**
     * Default constructor.
     */
    public JacksonJsonDoubleSerializer ( ) {

    }

    /**
     * Constructor.
     * @param precision number of digits after the decimal to format numbers.
     */
    public JacksonJsonDoubleSerializer ( int precision ) {
            this.precision = precision;
    }

    /**
     * Format to use.  Create an instance so it is shared between serialize calls.
     */
    private DecimalFormat format = null;

    /**
     *
     */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property ) throws JsonMappingException {
            FormatterPrecision precision = property.getAnnotation(FormatterPrecision.class);
            if ( precision != null ) {
                    return new JacksonJsonDoubleSerializer(precision.precision());
            }
            return this;
    }

    /**
     * Check that the format has been created.
     */
    private DecimalFormat getFormat () {
            if ( this.format == null ) {
                    // No format so create it
                    StringBuilder b = new StringBuilder("0.");
                    for ( int i = 0; i < this.precision; i++ ) {
                            b.append("0");
                    }
                    this.format = new DecimalFormat(b.toString());
            }
            return this.format;
    }

    /**
     * Serialize a double
     */
    @Override
    public void serialize(Double value, JsonGenerator jgen, SerializerProvider provider ) throws IOException {
            if ( (value == null) || value.isNaN() ) {
                    jgen.writeNull();
            }
            else {
                    DecimalFormat format = getFormat();
                    jgen.writeRawValue(format.format(value));
            }
    }
}

我正在使用 MixIn,因此该类具有:

public abstract class StationJacksonMixIn {

    @JsonCreator
    public StationJacksonMixIn () {

    }

    // Serializers to control formatting
    @JsonSerialize(using=JacksonJsonDoubleSerializer.class) 
    @FormatterPrecision(precision=6) abstract Double getLatitude();
    @JsonSerialize(using=JacksonJsonDoubleSerializer.class) 
    @FormatterPrecision(precision=6) abstract Double getLongitude();
}

最后,在 ObjectMapper 中启用 MixIn:

ObjectMapper objectMapper = new ObjectMapper().
                addMixIn(Station.class,StationJacksonMixIn.class);

它可以很好地提供在数据字段上全局应用的精度。


0
投票

在我最初的更复杂的尝试之后,我找到了一个效果很好的更简单的解决方案。这涉及仅在适合输出格式的情况下使用

BigDecimal
。例如,以下代码具有
getValueFormatted
方法,可将值格式化为正确的位数。在这种情况下,输出 JSON 使用短元素名称
v
而不是
value
,并且
@JsonProperty("v")
用于指示应使用
getValueFormatted
方法返回输出值。我没有放入 MixIn,但可以做到这一点。这会向普通对象添加一个方法,但这对于 REST 服务代码来说是可以的。

  /**
   * Return the data value.
   * Ignored for JSON output because use getValueFormatted instead.
   */
  @JsonIgnore
  public Double getValue () {
    return this.value;
  }

  /**
   * Return the value formatted to appropriate number of digits.
   * This is used with serialization to output.
   * Use for JSON output because it is formatted to the correct number of digits.
   * @return the data value formatted to the appropriate number of digits.
   */
  @JsonProperty("v")
  public BigDecimal getValueFormatted () {
    // BigDecimal does not handle the concept of NaN and therefore can't serialize those values as Double does.
    if ( (this.value == null) || this.value.isNaN() || this.value.isInfinite() ) {
      return null;
    }
    BigDecimal bd = new BigDecimal(this.value).setScale(this.valueDigits,RoundingMode.HALF_UP);
    return bd;
  }

0
投票

我在序列化浮点数时尝试了很多方法。但我没能成功。最后,我通过编写自定义序列化找到了解决方案。

根据此解决方案,所有 float 类型类都应使用 Jackson 进行序列化,以便十进制数(精度)为 4,如下所示。

自定义序列化器

import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class CustomFloatSerializer extends JsonSerializer<Float> {

    @Override
    public void serialize(Float value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(String.format("%.4f", value));
    }
}

Spring Boot 对象映射器配置 :

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

@Configuration
public class ObjectMapperConfiguration {

    @Autowired
    public void customize(ObjectMapper objectMapper) {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Float.class, new CustomFloatSerializer());
        objectMapper.registerModule(module);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.