Monday, August 19, 2013

How To display trailing zeros in Json within Spring 3 and Jackson

This post could be simply read as a revisitation of the one in which I explained How To format dates in Json within Spring 3. But since the problem to solve is different, I think it could be useful to write a new guide about it, even if the mechanics are quite the same.

I recently discovered that managing prices with floats is a bad idea, so I decided to change all of my "float price" variables to a more efficient BigDecimal type. I know, the use of BigDecimals is controversial too, but it presently fits my needs, so I decided to use them in my web application.

Anyway, prices types are not what I'm going to talk about now. What I discovered when I tried to put my values in a table obtained via Json, is that Json automatically trims out trailing zeros after the decimal mark. This means that if you have to display a price like 1.20 dollars, you'll probably display 1.2 instead.

So, what I needed was something that would have been able to pick up my BigDecimal from the model, format it in a string including trailing zeros, and pass it to my jsp as Json. All of this, using Spring and Jackson, a Java library for processing JSON.

I assume you've got a jsp (i.e. itemslist.jsp), a controller class (i.e. ItemController.java) and a simple java class for the objects you want to manage in your Json call (e.g. Item.java). Plus, you should have enabled Spring annotation based configuration (see the reference links at the bottom of this post).

The steps!
  1. Create a PriceJsonSerializer class (you can change the name depending on your needs) that extends JsonSerializer. Like this:
    public class PriceJsonSerializer 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());
      }
    }
    

    As you can see, the serialize method writes in a String format the BigDecimal, ensuring that it's scaled in order to display two digits after the decimal mark (you can customize this behaviour changing the setScale parameter "2" to a higher or lower number).

    As you can see, the serialize method sets up the format you want to use for your BigDecimal, writing it in a String format.

    Important: this means that your object will reach the jsp in a String format. So if you want to perform client-side arithmetics calculations on your values (e.g., in javascript) you'll have to parse them back to a number format. For example, if you have two values a = 2 and b = 3 in your jsp, writing a + b will perform an append procedure between strings, not a sum, giving you 32 instead of 5!

  2. In Item.java class, insert the @JsonSerialize annotation above the date getters:
    @JsonSerialize(using=PriceJsonSerializer.class)
    public BigDecimal getPrice() {
     return price;
    }
    
    @JsonSerialize(using=PriceJsonSerializer.class)
    public void setPrice(BigDecimal price) {
     this.price = price;
    }
    

I hope this works for you as it does for me. If not, comment and we'll try to figure out a proper solution. Well, your comments are welcome in any way :)

References

4 comments:

  1. Is there a way to acheive this with out converting BigDecmial to String, I mean I want to print price as a number in json, so that the web developer don't have to parse it to number.

    ReplyDelete
    Replies
    1. I have the same need as Sampath. In json i would like to still have it as a number with two decimals.

      Delete
  2. public class PriceJsonSerializer extends JsonSerializer {

    @Override
    public void serialize(BigDecimal value, JsonGenerator jgen, SerializerProvider provider)
    throws IOException, JsonProcessingException {
    jgen.writeNumber(value.setScale(2, BigDecimal.ROUND_HALF_UP).toString());
    }
    }

    Just use writeNumber method ,thats it :)

    ReplyDelete
  3. writeNumber does not work as mentioned. It returns as 23.4 but the desired output is 23.40.

    ReplyDelete