Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scale of deserialized BigDecimal are not always the same #632

Closed
benoit-lateltin opened this issue Nov 25, 2014 · 9 comments
Closed

Scale of deserialized BigDecimal are not always the same #632

benoit-lateltin opened this issue Nov 25, 2014 · 9 comments

Comments

@benoit-lateltin
Copy link

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

}
@cowtowncoder
Copy link
Member

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.

@azzoti
Copy link

azzoti commented Sep 7, 2015

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;

@benoit-lateltin
Copy link
Author

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

}

@gmjabs
Copy link

gmjabs commented Oct 12, 2015

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.

@cowtowncoder
Copy link
Member

cowtowncoder commented Oct 12, 2015

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.

@cowtowncoder
Copy link
Member

Assuming this is fixed.

@Jero786
Copy link

Jero786 commented Sep 24, 2018

@azzoti that was exactly what I'm looking for, and it work for me. Thanks!

@mjustin
Copy link

mjustin commented Jul 2, 2020

@cowtowncoder — I 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;
        }
    }
}

@cowtowncoder
Copy link
Member

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants