【问题标题】:Need Jackson serializer for Double and need to specify precision at runtime需要用于 Double 的 Jackson 序列化程序,并且需要在运行时指定精度
【发布时间】:2017-10-21 23:29:50
【问题描述】:

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

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

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

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

任何想法都将不胜感激。

【问题讨论】:

    标签: java json jackson serialization


    【解决方案1】:

    您可以使用 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;
    
    }
    

    希望这会有所帮助!

    【讨论】:

    • 这几乎可以工作。顺便说一句,你有一个错字“Serilaizer”。我现在遇到的问题是我正在使用 MixIn,这样我就不会用 Jackson 注释污染 POJO。 MixIn 看起来像:@JsonSerialize(using=JacksonJsonDoubleSerializer.class) @FormatterPrecision(precision=2) abstract Double getValue(); @FormatterPrecision 在该位置是不允许的。
    • 这是因为我已通过@Target({ElementType.FIELD)}) 将注释配置为仅在字段级别使用。您可以为方法、构造函数等进行配置。我已经通过添加 ElementType.METHOD 编辑了允许方法的答案
    • ElementType.METHOD 的建议奏效了。下面是我使用 DecimalFormat 而不是 BigDecimal 的解决方案,希望性能良好。
    • 按原样使用此代码和以下注释@JsonSerialize(using = DoubleContextualSerializer.class) @Precision(precision = 8) private Double value; 它不会阻止此值1e-8 保留在科学计数法中。找到后会尽快发布解决方案。
    【解决方案2】:

    我使用了大部分建议的代码,但执行了以下操作,它使用 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);
    

    在全局应用于数据字段的情况下,它可以很好地提供精度。

    【讨论】:

    • 虽然 mixin 选项很好,new DecimalFormat(pattern) 使用默认语言环境创建格式,对于某些地区(例如一些欧洲国家),这意味着十进制和千位分隔符与打印时默认使用的分隔符不同java中的双倍。因此,这可能会创建 json-incompabitle 数字格式。对我来说,使用 BigDecimal 进行舍入似乎更便携。
    • 很容易获得所需区域设置的 DecimalFormat(实际上是 NumberFormat)以遵守特定的小数分隔符格式,例如:NumberFormat formatter = NumberFormat.getInstance(Locale.US); formatter.setMinimumFractionDigits(5); System.out.println("1/3 = " + formatter.format(1d/3);
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-30
    • 1970-01-01
    • 2012-09-29
    • 2010-10-29
    • 2019-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多