NumberSerTest.java
package tools.jackson.databind.ser.jdk;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.StreamWriteFeature;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Unit tests for verifying serialization of simple basic non-structured
* types; primitives (and/or their wrappers), Strings.
*/
public class NumberSerTest extends DatabindTestUtil
{
private final ObjectMapper MAPPER = sharedMapper();
private final ObjectMapper NON_EMPTY_MAPPER = jsonMapperBuilder()
.changeDefaultPropertyInclusion(v -> v.withValueInclusion(JsonInclude.Include.NON_EMPTY))
.build()
;
static class IntWrapper {
public int i;
public IntWrapper(int value) { i = value; }
}
static class DoubleWrapper {
public double value;
public DoubleWrapper(double v) { value = v; }
}
static class BigDecimalWrapper {
public BigDecimal value;
public BigDecimalWrapper(BigDecimal v) { value = v; }
}
static class IntAsString {
@JsonFormat(shape=JsonFormat.Shape.STRING)
@JsonProperty("value")
public int foo = 3;
}
static class LongAsString {
@JsonFormat(shape=JsonFormat.Shape.STRING)
public long value = 4;
}
static class DoubleAsString {
@JsonFormat(shape=JsonFormat.Shape.STRING)
public double value = -0.5;
}
static class BigIntegerAsString {
@JsonFormat(shape=JsonFormat.Shape.STRING)
public BigInteger value = BigInteger.valueOf(123456L);
}
static class BigDecimalAsString {
@JsonFormat(shape=JsonFormat.Shape.STRING)
public BigDecimal value;
public BigDecimalAsString() { this(BigDecimal.valueOf(0.25)); }
public BigDecimalAsString(BigDecimal v) { value = v; }
}
static class NumberWrapper {
// ensure it will use `Number` as statically force type, when looking for serializer
@JsonSerialize(as=Number.class)
public Number value;
public NumberWrapper(Number v) { value = v; }
}
static class BigDecimalHolder {
private final BigDecimal value;
public BigDecimalHolder(String num) {
value = new BigDecimal(num);
}
public BigDecimal getValue() {
return value;
}
}
static class BigDecimalAsStringSerializer extends ValueSerializer<BigDecimal> {
private final DecimalFormat df = createDecimalFormatForDefaultLocale("0.0");
@Override
public void serialize(BigDecimal value, JsonGenerator gen, SerializationContext serializers) {
gen.writeString(df.format(value));
}
}
static class BigDecimalAsNumberSerializer extends ValueSerializer<BigDecimal> {
private final DecimalFormat df = createDecimalFormatForDefaultLocale("0.0");
@Override
public void serialize(BigDecimal value, JsonGenerator gen, SerializationContext serializers) {
gen.writeNumber(df.format(value));
}
}
@SuppressWarnings("serial")
static class MyBigDecimal extends BigDecimal {
public MyBigDecimal(String value) {
super(value);
}
}
static class Bean2519Typed {
public List<BigDecimal> values = new ArrayList<>();
}
static class Bean2519Untyped {
public Collection<BigDecimal> values = new HashSet<>();
}
/*
/**********************************************************************
/* Test methods: short/int/long/BigInteger
/**********************************************************************
*/
@Test
public void testShortArray() throws Exception
{
assertEquals("[0,1]", MAPPER.writeValueAsString(new short[] { 0, 1 }));
assertEquals("[2,3]", MAPPER.writeValueAsString(new Short[] { 2, 3 }));
}
@Test
public void testIntArray() throws Exception
{
assertEquals("[0,-3]", MAPPER.writeValueAsString(new int[] { 0, -3 }));
assertEquals("[13,9]", MAPPER.writeValueAsString(new Integer[] { 13, 9 }));
}
@Test
public void testLongArray() throws Exception
{
assertEquals("[-123,42]", MAPPER.writeValueAsString(new long[] { -123, 42 }));
assertEquals("[123,-999]", MAPPER.writeValueAsString(new Long[] { 123L, -999L }));
}
@Test
public void testBigInteger() throws Exception
{
BigInteger[] values = new BigInteger[] {
BigInteger.ONE, BigInteger.TEN, BigInteger.ZERO,
BigInteger.valueOf(1234567890L),
new BigInteger("123456789012345678901234568"),
new BigInteger("-1250000124326904597090347547457")
};
for (BigInteger value : values) {
String expected = value.toString();
assertEquals(expected, MAPPER.writeValueAsString(value));
}
}
/*
/**********************************************************************
/* Test methods, float/double/BigDecimal
/**********************************************************************
*/
/* Note: dealing with floating-point values is tricky; not sure if
* we can really use equality tests here... JDK does have decent
* conversions though, to retain accuracy and round-trippability.
* But still...
*/
@Test
public void testFloat() throws Exception
{
double[] values = new double[] {
0.0, 1.0, 0.1, -37.01, 999.99, 0.3, 33.3, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
};
for (double d : values) {
float f = (float) d;
String expected = String.valueOf(f);
if (Float.isNaN(f) || Float.isInfinite(f)) {
expected = "\""+expected+"\"";
}
assertEquals(expected, MAPPER.writeValueAsString(Float.valueOf(f)));
}
}
@Test
public void testDouble() throws Exception
{
double[] values = new double[] {
0.0, 1.0, 0.1, -37.01, 999.99, 0.3, 33.3, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
};
for (double d : values) {
String expected = String.valueOf(d);
if (Double.isNaN(d) || Double.isInfinite(d)) {
expected = "\""+d+"\"";
}
assertEquals(expected, MAPPER.writeValueAsString(Double.valueOf(d)));
}
}
@Test
public void testBigDecimal() throws Exception
{
Map<String, Object> map = new HashMap<String, Object>();
String PI_STR = "3.14159265";
map.put("pi", new BigDecimal(PI_STR));
String str = MAPPER.writeValueAsString(map);
assertEquals("{\"pi\":3.14159265}", str);
}
@Test
public void testBigDecimalAsPlainString() throws Exception
{
final ObjectMapper mapper = new ObjectMapper(JsonFactory.builder()
.enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN)
.build());
Map<String, Object> map = new HashMap<String, Object>();
String PI_STR = "3.00000000";
map.put("pi", new BigDecimal(PI_STR));
String str = mapper.writeValueAsString(map);
assertEquals("{\"pi\":3.00000000}", str);
}
@Test
public void testBigIntegerAsPlainTest() throws Exception
{
final String NORM_VALUE = "0.0000000005";
final BigDecimal BD_VALUE = new BigDecimal(NORM_VALUE);
final BigDecimalAsString INPUT = new BigDecimalAsString(BD_VALUE);
// by default, use the default `toString()`
assertEquals("{\"value\":\""+BD_VALUE.toString()+"\"}", MAPPER.writeValueAsString(INPUT));
// but can force to "plain" notation
final ObjectMapper m = jsonMapperBuilder()
.enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN)
.build();
assertEquals("{\"value\":\""+NORM_VALUE+"\"}", m.writeValueAsString(INPUT));
}
@Test
public void testBigDecimalAsString2519Typed() throws Exception
{
Bean2519Typed foo = new Bean2519Typed();
foo.values.add(new BigDecimal("2.34"));
final ObjectMapper mapper = jsonMapperBuilder()
.withConfigOverride(BigDecimal.class,
o -> o.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING)))
.build();
String json = mapper.writeValueAsString(foo);
assertEquals(a2q("{'values':['2.34']}"), json);
}
@Test
public void testBigDecimalAsString2519Untyped() throws Exception
{
Bean2519Untyped foo = new Bean2519Untyped();
foo.values.add(new BigDecimal("2.34"));
final ObjectMapper mapper = jsonMapperBuilder()
.withConfigOverride(BigDecimal.class,
o -> o.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING)))
.build();
String json = mapper.writeValueAsString(foo);
assertEquals(a2q("{'values':['2.34']}"), json);
}
@Test
public void testCustomSerializationBigDecimalAsString() throws Exception {
SimpleModule module = new SimpleModule();
module.addSerializer(BigDecimal.class, new BigDecimalAsStringSerializer());
ObjectMapper mapper = jsonMapperBuilder().addModule(module).build();
assertEquals(a2q("{'value':'2.0'}"), mapper.writeValueAsString(new BigDecimalHolder("2")));
}
@Test
public void testCustomSerializationBigDecimalAsNumber() throws Exception {
SimpleModule module = new SimpleModule();
module.addSerializer(BigDecimal.class, new BigDecimalAsNumberSerializer());
ObjectMapper mapper = jsonMapperBuilder().addModule(module).build();
assertEquals(a2q("{'value':2.0}"), mapper.writeValueAsString(new BigDecimalHolder("2")));
}
/*
/**********************************************************************
/* Test methods, as-String
/**********************************************************************
*/
@Test
public void testNumbersAsString() throws Exception
{
assertEquals(a2q("{'value':'3'}"), MAPPER.writeValueAsString(new IntAsString()));
assertEquals(a2q("{'value':'4'}"), MAPPER.writeValueAsString(new LongAsString()));
assertEquals(a2q("{'value':'-0.5'}"), MAPPER.writeValueAsString(new DoubleAsString()));
assertEquals(a2q("{'value':'0.25'}"), MAPPER.writeValueAsString(new BigDecimalAsString()));
assertEquals(a2q("{'value':'123456'}"), MAPPER.writeValueAsString(new BigIntegerAsString()));
}
@Test
public void testNumbersAsStringNonEmpty() throws Exception
{
assertEquals(a2q("{'value':'3'}"), NON_EMPTY_MAPPER.writeValueAsString(new IntAsString()));
assertEquals(a2q("{'value':'4'}"), NON_EMPTY_MAPPER.writeValueAsString(new LongAsString()));
assertEquals(a2q("{'value':'-0.5'}"), NON_EMPTY_MAPPER.writeValueAsString(new DoubleAsString()));
assertEquals(a2q("{'value':'0.25'}"), NON_EMPTY_MAPPER.writeValueAsString(new BigDecimalAsString()));
assertEquals(a2q("{'value':'123456'}"), NON_EMPTY_MAPPER.writeValueAsString(new BigIntegerAsString()));
}
@Test
public void testConfigOverridesForNumbers() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.withAllConfigOverrides(all -> { // could have used separate but test for funsies
all.findOrCreateOverride(Integer.TYPE) // for `int`
.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));
all.findOrCreateOverride(Double.TYPE) // for `double`
.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));
all.findOrCreateOverride(BigDecimal.class)
.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));
})
.build();
assertEquals(a2q("{'i':'3'}"),
mapper.writeValueAsString(new IntWrapper(3)));
assertEquals(a2q("{'value':'0.75'}"),
mapper.writeValueAsString(new DoubleWrapper(0.75)));
assertEquals(a2q("{'value':'-0.5'}"),
mapper.writeValueAsString(new BigDecimalWrapper(BigDecimal.valueOf(-0.5))));
}
@Test
public void testNumberType() throws Exception
{
assertEquals(a2q("{'value':1}"), MAPPER.writeValueAsString(new NumberWrapper(Byte.valueOf((byte) 1))));
assertEquals(a2q("{'value':2}"), MAPPER.writeValueAsString(new NumberWrapper(Short.valueOf((short) 2))));
assertEquals(a2q("{'value':3}"), MAPPER.writeValueAsString(new NumberWrapper(Integer.valueOf(3))));
assertEquals(a2q("{'value':4}"), MAPPER.writeValueAsString(new NumberWrapper(Long.valueOf(4L))));
assertEquals(a2q("{'value':0.5}"), MAPPER.writeValueAsString(new NumberWrapper(Float.valueOf(0.5f))));
assertEquals(a2q("{'value':0.05}"), MAPPER.writeValueAsString(new NumberWrapper(Double.valueOf(0.05))));
assertEquals(a2q("{'value':123}"), MAPPER.writeValueAsString(new NumberWrapper(BigInteger.valueOf(123))));
assertEquals(a2q("{'value':0.025}"), MAPPER.writeValueAsString(new NumberWrapper(BigDecimal.valueOf(0.025))));
}
@Test
public void testConfigOverrideJdkNumber() throws Exception {
ObjectMapper mapper = jsonMapperBuilder().withConfigOverride(BigDecimal.class,
c -> c.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING)))
.build();
String value = mapper.writeValueAsString(new BigDecimal("123.456"));
assertEquals(a2q("'123.456'"), value);
}
@Test
public void testConfigOverrideNonJdkNumber() throws Exception {
ObjectMapper mapper = jsonMapperBuilder().withConfigOverride(MyBigDecimal.class,
c -> c.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING)))
.build();
String value = mapper.writeValueAsString(new MyBigDecimal("123.456"));
assertEquals(a2q("'123.456'"), value);
}
// default locale is en_US
static DecimalFormat createDecimalFormatForDefaultLocale(final String pattern) {
return new DecimalFormat(pattern, new DecimalFormatSymbols(Locale.ENGLISH));
}
}