Need Jackson serializer for Double and need to specify precision at runtime

前端 未结 2 454
一个人的身影
一个人的身影 2021-01-11 14:22

There are many posts about creating Jackson serializers for numbers, currency, etc. For engineering applications, there is often a need to set the precision on numbers based

相关标签:
2条回答
  • 2021-01-11 15:23

    You may use Jackson's ContextualSerializer to achieve desired serialization as shown below.

    Firstly, create an annotation to capture precision

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

    Next, create a contextual serializer for Double type which looks for Precision annotation on the field to be serialized and then create a new serializer for the specified 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;
        }
    }
    

    Finally, annotate your field to use custom serializer and set precision

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

    Hope this helps!!

    0 讨论(0)
  • 2021-01-11 15:29

    I used most of the suggested code but did the following, which uses DecimalFormat to do the formatting, which required outputting the raw text:

    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));
                }
        }
    }
    

    I am using a MixIn, so that class has:

    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();
    }
    

    And, finally, enable the MixIn in the ObjectMapper:

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

    It works well to provide a precision where it applies globally on the data field.

    0 讨论(0)
提交回复
热议问题