Hi,
When I use the annotation @JsonUnwrapped to materialize a component bean, the scale of the BigDecimal deserialized is different. I've written a test case below to reproduce the issue.
Is there a way to control the scale of BigDecimal? Do I have to configure a custom deserializer?
(Test case reproduced with Jacskon 2.4.0 and 1.9.13 on Java 6)
Thanks,
Benoit
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigDecimal;
public class BigDecimalScaleTest {
private String json = "{" +
" \"name\": \"Ben\"," +
" \"first\": 12.00," +
" \"second\": 14.00" +
"}";
private ObjectMapper objectMapper = new ObjectMapper();
@Test
public void testScale() throws Exception {
MyDomain myDomain = objectMapper.readValue(json, MyDomain.class);
MyFlatDomain myFlatDomain = objectMapper.readValue(json, MyFlatDomain.class);
Assert.assertEquals(myDomain.getName(), myFlatDomain.getName());
Assert.assertEquals(myDomain.getFirst(), myFlatDomain.getFirst()); // 12.00 = 12.00
Assert.assertEquals(myDomain.getInnerDomain().getSecond(), myFlatDomain.getSecond());
// 14.0 != 14.00
}
private static class MyDomain {
private String name;
private BigDecimal first;
@JsonUnwrapped
private MyInnerDomain innerDomain;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getFirst() {
return first;
}
public void setFirst(BigDecimal first) {
this.first = first;
}
public MyInnerDomain getInnerDomain() {
return innerDomain;
}
public void setInnerDomain(MyInnerDomain innerDomain) {
this.innerDomain = innerDomain;
}
}
private static class MyInnerDomain {
private BigDecimal second;
public BigDecimal getSecond() {
return second;
}
public void setSecond(BigDecimal second) {
this.second = second;
}
}
private static class MyFlatDomain {
private String name;
private BigDecimal first;
private BigDecimal second;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getFirst() {
return first;
}
public void setFirst(BigDecimal first) {
this.first = first;
}
public BigDecimal getSecond() {
return second;
}
public void setSecond(BigDecimal second) {
this.second = second;
}
}
}
Odd. It'd seem scales should be the same. There isn't much support to change handling of BigDecimal, not sure why unwrapping would change anything.
Well, except... maybe it is due to buffering needed. If so, TokenBuffer would be the culprit.
Reproduced with jackson 2.5.1
Workaround possible with simple Deserializer:
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;
}
}
Then us the deserializer like this to make the test pass
private static class MyInnerDomain {
@JsonDeserialize(using=MoneyDeserializer.class)
private BigDecimal second;
I did the same kind of workarround using a JsonDeserializer but I used a module to make it global :
public class BigDecimalMoneyDeserializer extends JsonDeserializer<BigDecimal> {
@Override
public BigDecimal deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return jp.getDecimalValue().setScale(2, BigDecimal.ROUND_HALF_UP);
}
}
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
SimpleModule testModule = new SimpleModule("my-module", new Version(1, 0, 0, null))
.addDeserializer(BigDecimal.class, new BigDecimalMoneyDeserializer());
this.registerModule(testModule);
}
}
I'd stumbled on this when poking around before submitting #965, which seemed different, but due to @JsonUnwrapped also using TokenBuffer it sounds just like it, the scale changing as a side effect of coercion through double.
Patch 2.6.3 was just released, and it does contain one fix to BigDecimal handling w/ TokenBuffer.
May not be related to problem here, but worth mentioning.
Assuming this is fixed.
@azzoti that was exactly what I'm looking for, and it work for me. Thanks!
@cowtowncoder 鈥斅營 just ran across this in my own code. I've confirmed that the test case in the ticket fails in the latest version of Jackson: 2.11.1.
Here's the JUnit 5 test I wrote that verifies this same functionality:
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class BigDecimalTrailingZeroesTest {
private ObjectMapper objectMapper = new ObjectMapper();
@Test
public void bigDecimalTrailingZeroes() throws JsonProcessingException {
// Passes
assertEquals(new BigDecimal("5.00"),
objectMapper.readValue("{\"value\": 5.00}", BigDecimalHolder.class).getValue());
}
@Test
public void unwrappedBigDecimalTrailingZeroes() throws JsonProcessingException {
// org.opentest4j.AssertionFailedError:
// Expected :5.00
// Actual :5.0
assertEquals(new BigDecimal("5.00"),
objectMapper.readValue("{\"value\": 5.00}", NestedBigDecimalHolder.class).getHolder().getValue());
}
private static class BigDecimalHolder {
private BigDecimal value;
public BigDecimal getValue() {
return value;
}
public void setValue(BigDecimal value) {
this.value = value;
}
}
private static class NestedBigDecimalHolder {
@JsonUnwrapped
private BigDecimalHolder holder;
public BigDecimalHolder getHolder() {
return holder;
}
public void setHolder(BigDecimalHolder holder) {
this.holder = holder;
}
}
}
@mjustin Please file a new issue (with possible ref to this as background); I do not usually re-open closed issues as that complicates release notes significantly.
Most helpful comment
Reproduced with jackson 2.5.1
Workaround possible with simple Deserializer:
Then us the deserializer like this to make the test pass