Serialize a Double to 2 decimal places using Jackson - java

I'm using Jackson, with Spring MVC, to write out some simple objects as JSON. One of the objects, has an amount property, of type Double. (I know that Double should not be used as a monetary amount. However, this is not my code.)
In the JSON output, I'd like to restrict the amount to 2 decimal places. Currently it is shown as:
"amount":459.99999999999994
I've tried using Spring 3's #NumberFormat annotation, but haven't had success in that direction. Looks like others had issues too: MappingJacksonHttpMessageConverter's ObjectMapper does not use ConversionService when binding JSON to JavaBean propertiesenter link description here.
Also, I tried using the #JsonSerialize annotation, with a custom serializer.
In the model:
#JsonSerialize(using = CustomDoubleSerializer.class)
public Double getAmount()
And serializer implementation:
public class CustomDoubleSerializer extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
if (null == value) {
//write the word 'null' if there's no value available
jgen.writeNull();
} else {
final String pattern = ".##";
//final String pattern = "###,###,##0.00";
final DecimalFormat myFormatter = new DecimalFormat(pattern);
final String output = myFormatter.format(value);
jgen.writeNumber(output);
}
}
}
The CustomDoubleSerializer "appears" to work. However, can anyone suggest any other simpler (or more standard) way of doing this.

I know that Double should not be used as a monetary amount. However,
this is not my code.
Indeed, it should not. BigDecimal is a much better choice for storing monetary amounts because it is lossless and provides more control of the decimal places.
So for people who do have control over the code, it can be used like this:
double amount = 111.222;
setAmount(new BigDecimal(amount).setScale(2, BigDecimal.ROUND_HALF_UP));
That will serialize as 111.22. No custom serializers needed.

I had a similar situation in my project. I had added the formatting code to the setter method of the POJO. DecimalFormatter, Math and other classes ended up rounding off the value, however, my requirement was not to round off the value but only to limit display to 2 decimal places.
I recreated this scenario.
Product is a POJO which has a member Double amount.
JavaToJSON is a class that will create an instance of Product and convert it to JSON.
The setter setAmount in Product will take care of formatting to 2 decimal places.
Here is the complete code.
Product.java
package com;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Product {
private String name;
private Double amount;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getAmount() {
return amount;
}
public void setAmount(Double amount) {
BigDecimal bd = new BigDecimal(amount).setScale(2, RoundingMode.FLOOR);
this.amount = bd.doubleValue();
}
#Override
public String toString() {
return "Product [name=" + name + ", amount=" + amount + "]";
}
}
JavaToJSON.java
package com;
import java.io.File;
import java.io.IOException;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
public class JavaToJSON {
public static void main(String[] args){
ObjectMapper mapper = new ObjectMapper();
try {
Product product = new Product();
product.setName("TestProduct");
product.setAmount(Double.valueOf("459.99999999999994"));
// Convert product to JSON and write to file
mapper.writeValue(new File("d:\\user.json"), product);
// display to console
System.out.println(product);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
I haven't accumulated enough points so I am not able to upload the screenshots to show you the output.
Hope this helps.

Regarding what was stated above, I just wanted to fix a little something, so that people won't waste time on it as I did. One should actually use
BigDecimal.valueOf(amount).xxx
instead of
new BigDecimal(amount).xxx
and this is actually somehow critical. Because if you don't, your decimal amount will be messed up. This is a limitation of floating point representation, as stated here.

Best way I have seen till now is to create a customized serializer and #JsonSerializer(using=NewClass.class). Wanted to try with #JsonFormat(pattern=".##") or so, but it may not work according one comment of OP(I think the formatter does not honor that)
See here: https://github.com/FasterXML/jackson-databind/issues/632
public class MoneyDeserializer extends JsonDeserializer<BigDecimal> {
private NumberDeserializers.BigDecimalDeserializer delegate = NumberDeserializers.BigDecimalDeserializer.instance;
#Override
public BigDecimal deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
BigDecimal bd = delegate.deserialize(jp, ctxt);
bd = bd.setScale(2, RoundingMode.HALF_UP);
return bd;
}
}
BUT, although more convenient and less code is written, usually, deciding the scale of a field is concern of business logic, not part of (de)serialization. Be clear about that. Jackson should be able to just pass the data as-is.

Note that 459.99999999999994 is effectively 460 and is expected to be serialized in this way. So, your logic should be trickier than just dropping digits.
I might suggest something like:
Math.round(value*10)/10.0
You might want to put it into setter, and get rid of custom serialization.

Related

How to ignore a list entry causing a deserialization error in Jackson?

I'm trying to deserialize a JSON structure with Jackson and I'm working with a DTO that looks like this:
public class RootLevelDTO {
private List<ComplexEntry> complexEntries;
// ... other fields, not relevant
}
Now, the ComplexEntry can have sub-types, those have properties of enum types etc. A lot can go wrong here if the other side of the communication updates their API and e.g. adds another sub type or adds an enum literal.
What I would like to do is to tell Jackson:
if you encounter any databinding error during deserialization of the complexEntries field...
... do not throw an exception, but instead ignore this entry and continue with the next.
What I tried so far is to use a delegating deserializer for ComplexEntry:
public class ComplexEntryDeserializer extends StdDeserializer<ComplexEntry> {
private StdDeserializer<ComplexEntry> delegate;
public ComplexEntryDeserializer(StdDeserializer<ComplexEntry> delegate){
this.delegate = delegate;
}
public ComplexEntry deserialize(JsonParser p, DeserializationContext ctxt){
try {
return this.delegate.deserialize(p, ctxt);
}catch(Exception e){
// the list entry failed to deserialize, but we have to return *something* here
return null;
}
}
// ... other mandatory methods, not relevant here
}
This solution has the problem that it will introduce null values to the complexEntries list, which I then have to explicitly get rid of with a Converter.
Is there a more elegant solution to this problem?
After a lot of tinkering I've ended up with the following solution. It doesn't require any additional jackson modules or other magic, only a single (specific) deserializer.
DTO:
public class RootLevelDTO {
// use a custom deserializer for the list
#JsonDeserialize(using = ListOfComplexEntryDeserializer.class)
private List<ComplexEntry> complexEntries;
}
Deserializer:
public class ListOfComplexEntryDeserializer extends JsonDeserializer<List<ComplexEntry>> {
#Override
public List<ComplexEntry> deserialize(JsonParser p, DeserializationContext ctxt) {
List<ComplexEntry> resultList = new ArrayList<>();
while(p.nextToken() != JsonToken.END_ARRAY){
try {
// delegate the deserialization of the individual list entries to the standard deserializers
resultList.add(ctxt.readValue(p, ComplexEntry.class))
}catch(Exception e){
// log that the entry wasn't deserialized properly
System.out.println("ComplexEntry could not be read and will be ignored.");
}
}
return resultList;
}
}
Big disclaimer: While the code above works, it's not something you should go for by design. I'm really with my back to the wall here and have no other choice (due to external factors beyond my control), and for that case it works.

Big Decimal formatting after rounding

I am having an issue with Big Decimal and its formatting after rounding. I have an input price as 35.90 and the output returns 35.9
This is how I am doing my rounding:
BigDecimal scaledResult = rs.getPrice().setScale(2, BigDecimal.ROUND_HALF_UP);
sc.setPrice(scaledResult);
which returns the 35.9 output even though I have set the scale to two decimal places. Any ideas?
Thanks guys for your help, here is how I have solved it by writing a serializer:
public class BigDecimalSerializer extends JsonSerializer<BigDecimal> {
#Override
public void serialize(BigDecimal value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeString(value.setScale(2, BigDecimal.ROUND_HALF_UP).toString());
}
}
and fields of my model:
#JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal price;
Your rounding is working fine. The reason why you are not getting trailing zeros is that you are returning the response as BigDecimal, instead you need to return as String(as mentioned by others as well).
There is simple way of doing it using #JsonFormat annotation with shape as STRING on top of your BigDecimal variables in your Response Object. Refer below:
import com.fasterxml.jackson.annotation.JsonFormat;
class YourResponseObjectClass {
#JsonFormat (shape=JsonFormat.Shape.STRING)
private BigDecimal price;
}

set Jackson ObjectMapper class not to use scientific notation for double

I am using a library com.fasterxml.jackson library for JsonSchema,
I am creating an IntegerSchema object, when I set range for integer schema using below code:
main(){
IntegerSchema intSchema = new IntegerSchema();
// setMaximum accepts Double object
intSchema.setMaximum(new Double(102000000));
// setMaximum accepts Double object
intSchema.setMinimum(new Double(100));
printJsonSchema(intSchema);
}
public void printJsonSchema(JsonSchema schema){
ObjectMapper mapper = new ObjectMapper();
try {
logger.info(mapper.writeValueAsString(schema));
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
}
When I convert IntegerSchema to string using ObjectMapper getting below response:
{"type":"integer","maximum":1.02E8,"minimum":100.0}
maximum and minimum values are getting converted to scientific notation.
But I need output in non scientific notation as below:
{"type":"integer","maximum":102000000,"minimum":100}
I cannot change IntegerSchema class.
Please suggest how to get the required output without extending IntegerSchema class?
Thanks in advance
this is a java issue somewhat I believe. If you debug your program, your Double will always be displayed scientifically, so what we'll want is to force it into a String. This can be achieved in Java in multiple ways, and you can look it up here:
How to print double value without scientific notation using Java?
In terms of your specific question about Jackson, I've written up some code for you:
public class ObjectMapperTest {
public static void main(String[] args) throws JsonProcessingException {
IntegerSchema schema = new IntegerSchema();
schema.type = "Int";
schema.max = 10200000000d;
schema.min = 100d;
ObjectMapper m = new ObjectMapper();
System.out.println(m.writeValueAsString(schema));
}
public static class IntegerSchema {
#JsonProperty
String type;
#JsonProperty
double min;
#JsonProperty
#JsonSerialize(using=MyDoubleDesirializer.class)
double max;
}
public static class MyDoubleDesirializer extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
// TODO Auto-generated method stub
BigDecimal d = new BigDecimal(value);
gen.writeNumber(d.toPlainString());
}
}
}
The trick is to register a custom Serializer for your Double value. This way, you can control what you want.
I am using the BigDecimal value to create a String representation of your Double. The output then becomes (for the specific example):
{"type":"Int","min":100.0,"max":10200000000}
I hope that solves your problem.
Artur
Feature.WRITE_BIGDECIMAL_AS_PLAIN
set this for your Object Mapper
I know I am answering late, but something I faced may help other
While converting a BigDecimal I have faced below is working
mapper = mapper.setNodeFactory(JsonNodeFactory.withExactBigDecimals(true));
while this is not working for me
mapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
Update for Jakson 2.9.10:
Property WRITE_BIGDECIMAL_AS_PLAIN replaced to com.fasterxml.jackson.core.JsonGenerator. You could use:
mapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
If you are using ValueToTree then no need of any factory settings. only problem with ValueToTree is it is converting as TextNode (String Fromat), So if you have any logic based on ObjectNodes it will not work
You should use
mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
To avoid scientific notation on floating numbers.
You can find an example below.
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
String test ="{\"doubleValue\" : 0.00001}";
try {
System.out.println(mapper.readTree(test).toPrettyString());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
Output
{
"doubleValue" : 0.00001
}

Genson Polymorphic / Generic Serialization

I am trying to implement a JSON serialization in Java with Genson 1.3 for polymorphic types, including:
Numbers
Arrays
Enum classes
The SSCCE below demonstrates roughly what I am trying to achieve:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.owlike.genson.Genson;
import com.owlike.genson.GensonBuilder;
/**
* A Short, Self Contained, Compilable, Example for polymorphic serialization
* and deserialization.
*/
public class GensonPolymoprhicRoundTrip {
// our example enum
public static enum RainState {
NO_RAIN,
LIGHT_RAIN,
MODERATE_RAIN,
HEAVY_RAIN,
LIGHT_SNOW,
MODERATE_SNOW,
HEAVY_SNOW;
}
public static class Measurement<T> {
public T value;
public int qualityValue;
public String source;
public Measurement() {
}
public Measurement(T value, int qualityValue, String source) {
this.value = value;
this.qualityValue = qualityValue;
this.source = source;
}
}
public static class DTO {
public List<Measurement<?>> measurements;
public DTO(List<Measurement<?>> measurements) {
this.measurements = measurements;
}
}
public static void main(String... args) {
Genson genson = new GensonBuilder()
.useIndentation(true)
.useRuntimeType(true)
.useClassMetadataWithStaticType(false)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.create();
DTO dto = new DTO(
new ArrayList(Arrays.asList(
new Measurement<Double>(15.5, 8500, "TEMP_SENSOR"),
new Measurement<double[]>(new double[] {
2.5,
1.5,
2.0
}, 8500, "WIND_SPEED"),
new Measurement<RainState>(RainState.LIGHT_RAIN, 8500, "RAIN_SENSOR")
)));
String json = genson.serialize(dto);
System.out.println(json);
DTO deserialized = genson.deserialize(json, DTO.class);
}
}
Numbers and Arrays worked well out-of-the-box, but the enum class is providing a bit of a challenge. In this case the serialized JSON form would have to be IMO a JSON object including a:
type member
value member
Looking at the EnumConverter class I see that I would need to provide a custom Converter. However I can't quite grasp how to properly register the Converter so that it would be called during deserialization. How should this serialization be solved using Genson?
Great for providing a complete example!
First problem is that DTO doesn't have a no arg constructor, but Genson supports classes even with constructors that have arguments. You just have to enable it via the builder with 'useConstructorWithArguments(true)'.
However this will not solve the complete problem. For the moment Genson has full polymorphic support only for types that are serialized as a json object. Because Genson will add a property called '#class' to it. There is an open issue for that.
Probably the best solution that should work with most situations would be to define a converter that automatically wraps all the values in json objects, so the converter that handles class metadata will be able to generate it. This can be a "good enough" solution while waiting for it to be officially supported by Genson.
So first define the wrapping converter
public static class LiteralAsObjectConverter<T> implements Converter<T> {
private final Converter<T> concreteConverter;
public LiteralAsObjectConverter(Converter<T> concreteConverter) {
this.concreteConverter = concreteConverter;
}
#Override
public void serialize(T object, ObjectWriter writer, Context ctx) throws Exception {
writer.beginObject().writeName("value");
concreteConverter.serialize(object, writer, ctx);
writer.endObject();
}
#Override
public T deserialize(ObjectReader reader, Context ctx) throws Exception {
reader.beginObject();
T instance = null;
while (reader.hasNext()) {
reader.next();
if (reader.name().equals("value")) instance = concreteConverter.deserialize(reader, ctx);
else throw new IllegalStateException(String.format("Encountered unexpected property named '%s'", reader.name()));
}
reader.endObject();
return instance;
}
}
Then you need to register it with a ChainedFactory which would allow you to delegate to the default converter (this way it works automatically with any other type).
Genson genson = new GensonBuilder()
.useIndentation(true)
.useConstructorWithArguments(true)
.useRuntimeType(true)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.withConverterFactory(new ChainedFactory() {
#Override
protected Converter<?> create(Type type, Genson genson, Converter<?> nextConverter) {
if (Wrapper.toAnnotatedElement(nextConverter).isAnnotationPresent(HandleClassMetadata.class)) {
return new LiteralAsObjectConverter(nextConverter);
} else {
return nextConverter;
}
}
}).create();
The downside with this solution is that useClassMetadataWithStaticType needs to be set to true...but well I guess it is acceptable as it's an optim and can be fixed but would imply some changes in Gensons code, the rest still works.
If you are feeling interested by this problem it would be great you attempted to give a shot to that issue and open a PR to provide this feature as part of Genson.

BigDecimal has scientific notation in soap message

I have got strange problem with our webservice.
I have got object OrderPosition which has got a price (which is xsd:decimal with fractionDigits = 9). Apache CXF generate proxy classes for me, and this field is BigDecimal. When I'd like to send value greater than 10000000.00000, this field in soap message has got scientific notation (for example 1.1423E+7).
How can I enforce that the value has not been sent in scientific notation.
Here is one way this can be done.
BigDecimal has a constructor which takes input number as a string. This when used, preservs the input formatting when its .toString() method is called. e.g.
BigDecimal bd = new BigDecimal("10000000.00000");
System.out.println(bd);
will print 10000000.00000.
This can be leveraged in Jaxb XmlAdapters. Jaxb XmlAdapters offer a convenient way to control/customize the marshalling/unmashalling process. A typical adapter for BigDecimmal would look like as follows.
public class BigDecimalXmlAdapter extends XmlAdapter{
#Override
public String marshal(BigDecimal bigDecimal) throws Exception {
if (bigDecimal != null){
return bigDecimal.toString();
}
else {
return null;
}
}
#Override
public BigDecimal unmarshal(String s) throws Exception {
try {
return new BigDecimal(s);
} catch (NumberFormatException e) {
return null;
}
}
}
This needs to be registered with Jaxb context. Here is the link with complete example.
#Santosh Thank you! XMLAdapter was what I needed.
Furthermore as I said in my question I generate client classes with Apache CXF. In this kind of problem I had to add the following code to bindings.xjb (binding file which is use to cxf-codegen-plugin in maven).
<jaxb:javaType name="java.math.BigDecimal" xmlType="xs:decimal"
parseMethod="sample.BigDecimalFormater.parseBigDecimal"
printMethod="sample.BigDecimalFormater.printBigDecimal" />
This is my formatter code:
public class BigDecimalFormater {
public static String printBigDecimal(BigDecimal value) {
value.setScale(5);
return value.toPlainString();
}
public static BigDecimal parseBigDecimal(String value) {
return new BigDecimal(value);
}
}
Then this plugin generate Adapter for me
public class Adapter1 extends XmlAdapter<String, BigDecimal> {
public BigDecimal unmarshal(String value) {
return (sample.BigDecimalFormater.parseBigDecimal(value));
}
public String marshal(BigDecimal value) {
return (sample.BigDecimalFormater.printBigDecimal(value));
}
}
In generated class BigDecimal field has got annotation #XmlJavaTypeAdapter(Adapter1 .class), and it resolved problem.

Categories