diff --git a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java index 5be6dfc730..43ab3dc447 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java @@ -1849,7 +1849,8 @@ public NumberType getNumberType() throws IOException if (n instanceof Float) return NumberType.FLOAT; if (n instanceof Short) return NumberType.INT; // should be SHORT if (n instanceof String) { - return ((String) n).contains(".") ? NumberType.BIG_DECIMAL : NumberType.BIG_INTEGER; + return (_currToken == JsonToken.VALUE_NUMBER_FLOAT) + ? NumberType.BIG_DECIMAL : NumberType.BIG_INTEGER; } return null; } @@ -1876,22 +1877,35 @@ private Number getNumberValue(final boolean preferBigNumbers) throws IOException // try to determine Double/BigDecimal preference... if (value instanceof String) { String str = (String) value; - if (str.indexOf('.') >= 0) { - if (preferBigNumbers) { - return NumberInput.parseBigDecimal(str, - isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER)); + final int len = str.length(); + if (_currToken == JsonToken.VALUE_NUMBER_INT) { + if (preferBigNumbers + // 01-Feb-2023, tatu: Not really accurate but we'll err on side + // of not losing accuracy (should really check 19-char case, + // or, with minus sign, 20-char) + || (len >= 19)) { + return NumberInput.parseBigInteger(str, isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER)); } - return NumberInput.parseDouble(str, isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER)); - } else if (preferBigNumbers) { - return NumberInput.parseBigInteger(str, isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER)); + // Otherwise things get trickier; here, too, we should use more accurate + // boundary checks + if (len >= 10) { + return NumberInput.parseLong(str); + } + return NumberInput.parseInt(str); } - return NumberInput.parseLong(str); - } - if (value == null) { - return null; + if (preferBigNumbers) { + BigDecimal dec = NumberInput.parseBigDecimal(str, + isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER)); + // 01-Feb-2023, tatu: This is... weird. Seen during tests, only + if (dec == null) { + throw new IllegalStateException("Internal error: failed to parse number '"+str+"'"); + } + return dec; + } + return NumberInput.parseDouble(str, isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER)); } throw new IllegalStateException("Internal error: entry should be a Number, but is of type " - +value.getClass().getName()); + +ClassUtil.classNameOf(value)); } private final boolean _smallerThanInt(Number n) { diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/lazy/LazyIgnoralForNumbers3730Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/lazy/LazyIgnoralForNumbers3730Test.java index f42f26e445..b1c0667d9b 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/lazy/LazyIgnoralForNumbers3730Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/lazy/LazyIgnoralForNumbers3730Test.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; - +import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.core.io.NumberInput; import com.fasterxml.jackson.databind.BaseMapTest; @@ -49,23 +49,40 @@ public int getI() { } // Another class to test that we do actually call parse method -- just not - // eagerly - static class ExtractFieldsWithNumberField3730 { - public String s; - public int i; - public Number n; + // eagerly. But MUST use "@JsonUnwrapped" to force buffering; creator not enough + static class UnwrappedWithNumber { + @JsonUnwrapped + public Values values; + + static class Values { + public String s; + public int i; + public Number n; + } } - static class ExtractFieldsWithBigDecimalField3730 { - public String s; - public int i; - public double n; + // Same as above + static class UnwrappedWithBigDecimal { + @JsonUnwrapped + public Values values; + + static class Values { + public String s; + public int i; + public BigDecimal n; + } } - static class ExtractFieldsWithDoubleField3730 { - public String s; - public int i; - public BigDecimal n; + // And same here + static class UnwrappedWithDouble { + @JsonUnwrapped + public Values values; + + static class Values { + public String s; + public int i; + public double n; + } } /* @@ -78,6 +95,10 @@ static class ExtractFieldsWithDoubleField3730 { .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); + private final ObjectMapper STRICT_MAPPER = JsonMapper.builder() + .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + public void testIgnoreBigInteger() throws Exception { final String MOCK_MSG = "mock: deliberate failure for parseBigInteger"; @@ -95,11 +116,11 @@ public void testIgnoreBigInteger() throws Exception ExtractFieldsNoDefaultConstructor3730 ef = MAPPER.readValue(json, ExtractFieldsNoDefaultConstructor3730.class); assertNotNull(ef); - - // Ok but then let's ensure method IS called, if field is actually mapped + // Ok but then let's ensure method IS called, if field is actually mapped, + // first to Number try { - MAPPER.readValue(json, ExtractFieldsWithNumberField3730.class); - fail("Should throw exception with mocking!"); + Object ob = STRICT_MAPPER.readValue(json, UnwrappedWithNumber.class); + fail("Should throw exception with mocking: instead got: "+MAPPER.writeValueAsString(ob)); } catch (DatabindException e) { verifyMockException(e, MOCK_MSG); } @@ -122,7 +143,7 @@ public void testIgnoreFPValuesDefault() throws Exception // Ok but then let's ensure method IS called, if field is actually mapped // First, to "Number" try { - MAPPER.readValue(json, ExtractFieldsWithNumberField3730.class); + STRICT_MAPPER.readValue(json, UnwrappedWithNumber.class); fail("Should throw exception with mocking!"); } catch (DatabindException e) { verifyMockException(e, MOCK_MSG); @@ -130,20 +151,19 @@ public void testIgnoreFPValuesDefault() throws Exception // And then to "double" // 01-Feb-2023, tatu: Not quite working, yet: - /* try { - MAPPER.readValue(json, ExtractFieldsWithDoubleField3730.class); + STRICT_MAPPER.readValue(json, UnwrappedWithDouble.class); fail("Should throw exception with mocking!"); } catch (DatabindException e) { + e.printStackTrace(); verifyMockException(e, MOCK_MSG); } - */ } public void testIgnoreFPValuesBigDecimal() throws Exception { final String MOCK_MSG = "mock: deliberate failure for parseBigDecimal"; - final ObjectReader reader = MAPPER + ObjectReader reader = MAPPER .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) .readerFor(ExtractFieldsNoDefaultConstructor3730.class); @@ -159,10 +179,13 @@ public void testIgnoreFPValuesBigDecimal() throws Exception reader.readValue(genJson(json)); assertNotNull(ef); + // But then ensure we'll fail with unknown (except does it work with unwrapped?) + reader = reader.with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + // Ok but then let's ensure method IS called, if field is actually mapped // First to Number try { - reader.forType(ExtractFieldsWithNumberField3730.class).readValue(json); + reader.forType(UnwrappedWithNumber.class).readValue(json); fail("Should throw exception with mocking!"); } catch (DatabindException e) { verifyMockException(e, MOCK_MSG); @@ -170,14 +193,12 @@ public void testIgnoreFPValuesBigDecimal() throws Exception // And then to "BigDecimal" // 01-Feb-2023, tatu: Not quite working, yet: - /* try { - reader.forType(ExtractFieldsWithBigDecimalField3730.class).readValue(json); + reader.forType(UnwrappedWithBigDecimal.class).readValue(json); fail("Should throw exception with mocking!"); } catch (DatabindException e) { verifyMockException(e, MOCK_MSG); } - */ } private String genJson(String num) {