问题
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 on the units or other criteria.
For example, spatial coordinates might be constrained to 5 or 6 digits after the decimal point, and temperature might be constrained to 2 digits after the decimal point. Default serializer behavior that has too many digits or truncated exponential notation is not good. What I need is something like:
@JsonSerialize(using=MyDoubleSerializer.class, precision=6) double myValue;
and better yet be able to specify the precision at run-time. I am also using a MixIn. I could write a serializer for each class but hoped to specify on specific values.
Any ideas would be appreciated.
回答1:
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!!
回答2:
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.
来源:https://stackoverflow.com/questions/44110624/need-jackson-serializer-for-double-and-need-to-specify-precision-at-runtime