package com.fasterxml.jackson.databind.node; import java.math.BigDecimal; import java.math.BigInteger; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.*; /** * Basic tests for {@link JsonNode} implementations that * contain numeric values. */ public class NumberNodesTest extends NodeTestBase { private final ObjectMapper MAPPER = objectMapper(); public void testShort() { ShortNode n = ShortNode.valueOf((short) 1); assertStandardEquals(n); assertTrue(0 != n.hashCode()); assertEquals(JsonToken.VALUE_NUMBER_INT, n.asToken()); assertEquals(JsonParser.NumberType.INT, n.numberType()); // should be SHORT assertEquals(1, n.intValue()); assertEquals(1L, n.longValue()); assertEquals(BigDecimal.ONE, n.decimalValue()); assertEquals(BigInteger.ONE, n.bigIntegerValue()); assertEquals("1", n.asText()); assertNodeNumbers(n, 1, 1.0); assertTrue(ShortNode.valueOf((short) 0).canConvertToInt()); assertTrue(ShortNode.valueOf(Short.MAX_VALUE).canConvertToInt()); assertTrue(ShortNode.valueOf(Short.MIN_VALUE).canConvertToInt()); assertTrue(ShortNode.valueOf((short) 0).canConvertToLong()); assertTrue(ShortNode.valueOf(Short.MAX_VALUE).canConvertToLong()); assertTrue(ShortNode.valueOf(Short.MIN_VALUE).canConvertToLong()); } public void testIntViaMapper() throws Exception { int value = -90184; JsonNode result = MAPPER.readTree(String.valueOf(value)); assertTrue(result.isNumber()); assertTrue(result.isIntegralNumber()); assertTrue(result.isInt()); assertType(result, IntNode.class); assertFalse(result.isLong()); assertFalse(result.isFloatingPointNumber()); assertFalse(result.isDouble()); assertFalse(result.isNull()); assertFalse(result.isTextual()); assertFalse(result.isMissingNode()); assertEquals(value, result.numberValue().intValue()); assertEquals(value, result.intValue()); assertEquals(String.valueOf(value), result.asText()); assertEquals((double) value, result.doubleValue()); assertEquals((long) value, result.longValue()); // also, equality should work ok assertEquals(result, IntNode.valueOf(value)); } public void testInt() { IntNode n = IntNode.valueOf(1); assertStandardEquals(n); assertTrue(0 != n.hashCode()); assertEquals(JsonToken.VALUE_NUMBER_INT, n.asToken()); assertEquals(JsonParser.NumberType.INT, n.numberType()); assertEquals(1, n.intValue()); assertEquals(1L, n.longValue()); assertEquals(BigDecimal.ONE, n.decimalValue()); assertEquals(BigInteger.ONE, n.bigIntegerValue()); assertEquals("1", n.asText()); // 2.4 assertEquals("1", n.asText("foo")); assertNodeNumbers(n, 1, 1.0); assertTrue(IntNode.valueOf(0).canConvertToInt()); assertTrue(IntNode.valueOf(Integer.MAX_VALUE).canConvertToInt()); assertTrue(IntNode.valueOf(Integer.MIN_VALUE).canConvertToInt()); assertTrue(IntNode.valueOf(0).canConvertToLong()); assertTrue(IntNode.valueOf(Integer.MAX_VALUE).canConvertToLong()); assertTrue(IntNode.valueOf(Integer.MIN_VALUE).canConvertToLong()); } public void testLong() { LongNode n = LongNode.valueOf(1L); assertStandardEquals(n); assertTrue(0 != n.hashCode()); assertEquals(JsonToken.VALUE_NUMBER_INT, n.asToken()); assertEquals(JsonParser.NumberType.LONG, n.numberType()); assertEquals(1, n.intValue()); assertEquals(1L, n.longValue()); assertEquals(BigDecimal.ONE, n.decimalValue()); assertEquals(BigInteger.ONE, n.bigIntegerValue()); assertEquals("1", n.asText()); assertNodeNumbers(n, 1, 1.0); // ok if contains small enough value assertTrue(LongNode.valueOf(0).canConvertToInt()); assertTrue(LongNode.valueOf(Integer.MAX_VALUE).canConvertToInt()); assertTrue(LongNode.valueOf(Integer.MIN_VALUE).canConvertToInt()); // but not in other cases assertFalse(LongNode.valueOf(1L + Integer.MAX_VALUE).canConvertToInt()); assertFalse(LongNode.valueOf(-1L + Integer.MIN_VALUE).canConvertToInt()); assertTrue(LongNode.valueOf(0L).canConvertToLong()); assertTrue(LongNode.valueOf(Long.MAX_VALUE).canConvertToLong()); assertTrue(LongNode.valueOf(Long.MIN_VALUE).canConvertToLong()); } public void testLongViaMapper() throws Exception { // need to use something being 32-bit value space long value = 12345678L << 32; JsonNode result = MAPPER.readTree(String.valueOf(value)); assertTrue(result.isNumber()); assertTrue(result.isIntegralNumber()); assertTrue(result.isLong()); assertType(result, LongNode.class); assertFalse(result.isInt()); assertFalse(result.isFloatingPointNumber()); assertFalse(result.isDouble()); assertFalse(result.isNull()); assertFalse(result.isTextual()); assertFalse(result.isMissingNode()); assertEquals(value, result.numberValue().longValue()); assertEquals(value, result.longValue()); assertEquals(String.valueOf(value), result.asText()); assertEquals((double) value, result.doubleValue()); // also, equality should work ok assertEquals(result, LongNode.valueOf(value)); } public void testDouble() throws Exception { DoubleNode n = DoubleNode.valueOf(0.25); assertStandardEquals(n); assertTrue(0 != n.hashCode()); assertEquals(JsonToken.VALUE_NUMBER_FLOAT, n.asToken()); assertEquals(JsonParser.NumberType.DOUBLE, n.numberType()); assertEquals(0, n.intValue()); assertEquals(0.25, n.doubleValue()); assertNotNull(n.decimalValue()); assertEquals(BigInteger.ZERO, n.bigIntegerValue()); assertEquals("0.25", n.asText()); assertNodeNumbers(DoubleNode.valueOf(4.5), 4, 4.5); assertTrue(DoubleNode.valueOf(0).canConvertToInt()); assertTrue(DoubleNode.valueOf(Integer.MAX_VALUE).canConvertToInt()); assertTrue(DoubleNode.valueOf(Integer.MIN_VALUE).canConvertToInt()); assertFalse(DoubleNode.valueOf(1L + Integer.MAX_VALUE).canConvertToInt()); assertFalse(DoubleNode.valueOf(-1L + Integer.MIN_VALUE).canConvertToInt()); assertTrue(DoubleNode.valueOf(0L).canConvertToLong()); assertTrue(DoubleNode.valueOf(Long.MAX_VALUE).canConvertToLong()); assertTrue(DoubleNode.valueOf(Long.MIN_VALUE).canConvertToLong()); JsonNode num = objectMapper().readTree(" -0.0"); assertTrue(num.isDouble()); n = (DoubleNode) num; assertEquals(-0.0, n.doubleValue()); assertEquals("-0.0", String.valueOf(n.doubleValue())); } public void testDoubleViaMapper() throws Exception { double value = 3.04; JsonNode result = MAPPER.readTree(String.valueOf(value)); assertTrue(result.isNumber()); assertFalse(result.isNull()); assertType(result, DoubleNode.class); assertTrue(result.isFloatingPointNumber()); assertTrue(result.isDouble()); assertFalse(result.isInt()); assertFalse(result.isLong()); assertFalse(result.isIntegralNumber()); assertFalse(result.isTextual()); assertFalse(result.isMissingNode()); assertEquals(value, result.doubleValue()); assertEquals(value, result.numberValue().doubleValue()); assertEquals((int) value, result.intValue()); assertEquals((long) value, result.longValue()); assertEquals(String.valueOf(value), result.asText()); // also, equality should work ok assertEquals(result, DoubleNode.valueOf(value)); } // @since 2.2 public void testFloat() { FloatNode n = FloatNode.valueOf(0.45f); assertStandardEquals(n); assertTrue(0 != n.hashCode()); assertEquals(JsonToken.VALUE_NUMBER_FLOAT, n.asToken()); assertEquals(JsonParser.NumberType.FLOAT, n.numberType()); assertEquals(0, n.intValue()); // NOTE: conversion to double NOT as simple as with exact numbers like 0.25: assertEquals(0.45f, n.floatValue()); assertEquals("0.45", n.asText()); // so; as double we'll get more complex number; however, should round-trip // to something that gets printed the same way. But not exact value, alas, hence: assertEquals("0.45", String.valueOf((float) n.doubleValue())); assertNotNull(n.decimalValue()); // possibly surprisingly, however, this will produce same output: assertEquals(BigInteger.ZERO, n.bigIntegerValue()); assertEquals("0.45", n.asText()); // 1.6: assertNodeNumbers(FloatNode.valueOf(4.5f), 4, 4.5f); assertTrue(FloatNode.valueOf(0).canConvertToInt()); assertTrue(FloatNode.valueOf(Integer.MAX_VALUE).canConvertToInt()); assertTrue(FloatNode.valueOf(Integer.MIN_VALUE).canConvertToInt()); // rounding errors if we just add/sub 1... so: assertFalse(FloatNode.valueOf(1000L + Integer.MAX_VALUE).canConvertToInt()); assertFalse(FloatNode.valueOf(-1000L + Integer.MIN_VALUE).canConvertToInt()); assertTrue(FloatNode.valueOf(0L).canConvertToLong()); assertTrue(FloatNode.valueOf(Integer.MAX_VALUE).canConvertToLong()); assertTrue(FloatNode.valueOf(Integer.MIN_VALUE).canConvertToLong()); } public void testDecimalNode() throws Exception { DecimalNode n = DecimalNode.valueOf(BigDecimal.ONE); assertStandardEquals(n); assertTrue(n.equals(new DecimalNode(BigDecimal.ONE))); assertEquals(JsonToken.VALUE_NUMBER_FLOAT, n.asToken()); assertEquals(JsonParser.NumberType.BIG_DECIMAL, n.numberType()); assertTrue(n.isNumber()); assertFalse(n.isIntegralNumber()); assertFalse(n.isArray()); assertTrue(n.isBigDecimal()); assertEquals(BigDecimal.ONE, n.numberValue()); assertEquals(1, n.intValue()); assertEquals(1L, n.longValue()); assertEquals(BigDecimal.ONE, n.decimalValue()); assertEquals("1", n.asText()); assertNodeNumbers(n, 1, 1.0); assertTrue(DecimalNode.valueOf(BigDecimal.ZERO).canConvertToInt()); assertTrue(DecimalNode.valueOf(BigDecimal.valueOf(Integer.MAX_VALUE)).canConvertToInt()); assertTrue(DecimalNode.valueOf(BigDecimal.valueOf(Integer.MIN_VALUE)).canConvertToInt()); assertFalse(DecimalNode.valueOf(BigDecimal.valueOf(1L + Integer.MAX_VALUE)).canConvertToInt()); assertFalse(DecimalNode.valueOf(BigDecimal.valueOf(-1L + Integer.MIN_VALUE)).canConvertToInt()); assertTrue(DecimalNode.valueOf(BigDecimal.ZERO).canConvertToLong()); assertTrue(DecimalNode.valueOf(BigDecimal.valueOf(Long.MAX_VALUE)).canConvertToLong()); assertTrue(DecimalNode.valueOf(BigDecimal.valueOf(Long.MIN_VALUE)).canConvertToLong()); // no "natural" way to get it, must construct BigDecimal value = new BigDecimal("0.1"); JsonNode result = DecimalNode.valueOf(value); assertFalse(result.isObject()); assertTrue(result.isNumber()); assertFalse(result.isIntegralNumber()); assertFalse(result.isLong()); assertType(result, DecimalNode.class); assertFalse(result.isInt()); assertTrue(result.isFloatingPointNumber()); assertTrue(result.isBigDecimal()); assertFalse(result.isDouble()); assertFalse(result.isNull()); assertFalse(result.isTextual()); assertFalse(result.isMissingNode()); assertEquals(value, result.numberValue()); assertEquals(value.toString(), result.asText()); // also, equality should work ok assertEquals(result, DecimalNode.valueOf(value)); } public void testDecimalNodeEqualsHashCode() { // We want DecimalNodes with equivalent _numeric_ values to be equal; // this is not the case for BigDecimal where "1.0" and "1" are not // equal! BigDecimal b1 = BigDecimal.ONE; BigDecimal b2 = new BigDecimal("1.0"); BigDecimal b3 = new BigDecimal("0.01e2"); BigDecimal b4 = new BigDecimal("1000e-3"); DecimalNode node1 = new DecimalNode(b1); DecimalNode node2 = new DecimalNode(b2); DecimalNode node3 = new DecimalNode(b3); DecimalNode node4 = new DecimalNode(b4); assertEquals(node1.hashCode(), node2.hashCode()); assertEquals(node2.hashCode(), node3.hashCode()); assertEquals(node3.hashCode(), node4.hashCode()); assertEquals(node1, node2); assertEquals(node2, node1); assertEquals(node2, node3); assertEquals(node3, node4); } public void testBigIntegerNode() throws Exception { BigIntegerNode n = BigIntegerNode.valueOf(BigInteger.ONE); assertStandardEquals(n); assertTrue(n.equals(new BigIntegerNode(BigInteger.ONE))); assertEquals(JsonToken.VALUE_NUMBER_INT, n.asToken()); assertEquals(JsonParser.NumberType.BIG_INTEGER, n.numberType()); assertTrue(n.isNumber()); assertTrue(n.isIntegralNumber()); assertTrue(n.isBigInteger()); assertEquals(BigInteger.ONE, n.numberValue()); assertEquals(1, n.intValue()); assertEquals(1L, n.longValue()); assertEquals(BigInteger.ONE, n.bigIntegerValue()); assertEquals("1", n.asText()); assertNodeNumbers(n, 1, 1.0); BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); n = BigIntegerNode.valueOf(maxLong); assertEquals(Long.MAX_VALUE, n.longValue()); ObjectMapper mapper = new ObjectMapper(); JsonNode n2 = mapper.readTree(maxLong.toString()); assertEquals(Long.MAX_VALUE, n2.longValue()); // then over long limit: BigInteger beyondLong = maxLong.shiftLeft(2); // 4x max long n2 = mapper.readTree(beyondLong.toString()); assertEquals(beyondLong, n2.bigIntegerValue()); assertTrue(BigIntegerNode.valueOf(BigInteger.ZERO).canConvertToInt()); assertTrue(BigIntegerNode.valueOf(BigInteger.valueOf(Integer.MAX_VALUE)).canConvertToInt()); assertTrue(BigIntegerNode.valueOf(BigInteger.valueOf(Integer.MIN_VALUE)).canConvertToInt()); assertFalse(BigIntegerNode.valueOf(BigInteger.valueOf(1L + Integer.MAX_VALUE)).canConvertToInt()); assertFalse(BigIntegerNode.valueOf(BigInteger.valueOf(-1L + Integer.MIN_VALUE)).canConvertToInt()); assertTrue(BigIntegerNode.valueOf(BigInteger.ZERO).canConvertToLong()); assertTrue(BigIntegerNode.valueOf(BigInteger.valueOf(Long.MAX_VALUE)).canConvertToLong()); assertTrue(BigIntegerNode.valueOf(BigInteger.valueOf(Long.MIN_VALUE)).canConvertToLong()); } public void testBigDecimalAsPlain() throws Exception { ObjectMapper mapper = new ObjectMapper() .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) .enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN); final String INPUT = "{\"x\":1e2}"; final JsonNode node = mapper.readTree(INPUT); String result = mapper.writeValueAsString(node); assertEquals("{\"x\":100}", result); // also via ObjectWriter: assertEquals("{\"x\":100}", mapper.writer().writeValueAsString(node)); // and once more for [core#175]: BigDecimal bigDecimal = new BigDecimal(100); JsonNode tree = mapper.valueToTree(bigDecimal); assertEquals("100", mapper.writeValueAsString(tree)); } // Related to [databind#333] public void testCanonicalNumbers() throws Exception { JsonNodeFactory f = new JsonNodeFactory(); NumericNode n = f.numberNode(123); assertTrue(n.isInt()); n = f.numberNode(1L + Integer.MAX_VALUE); assertFalse(n.isInt()); assertTrue(n.isLong()); // 19-May-2015, tatu: Actually, no, coercion should not happen by default. // But it should be possible to change it if necessary. // but "too small" number will be 'int'... n = f.numberNode(123L); assertTrue(n.isLong()); } }