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

Be more lenient about number parsing #725

Merged
merged 1 commit into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,22 @@ private DefaultTypeConverterFactory() {
converters.put(String.class, Function.identity()::apply);
converters.put(boolean.class, DefaultTypeConverterFactory::convertBoolean);
converters.put(Boolean.class, DefaultTypeConverterFactory::convertBoolean);
converters.put(Integer.class, Integer::valueOf);
converters.put(int.class, Integer::valueOf);
converters.put(long.class, Long::valueOf);
converters.put(Long.class, Long::valueOf);
converters.put(short.class, Short::valueOf);
converters.put(Short.class, Short::valueOf);
converters.put(byte.class, Byte::valueOf);
converters.put(Byte.class, Byte::valueOf);
converters.put(double.class, Double::valueOf);
converters.put(Double.class, Double::valueOf);
converters.put(float.class, Float::valueOf);
converters.put(Float.class, Float::valueOf);
converters.put(Integer.class, Lenient::parseInt);
converters.put(int.class, Lenient::parseInt);
converters.put(long.class, Lenient::parseLong);
converters.put(Long.class, Lenient::parseLong);
converters.put(short.class, Lenient::parseShort);
converters.put(Short.class, Lenient::parseShort);
converters.put(byte.class, Lenient::parseByte);
converters.put(Byte.class, Lenient::parseByte);
converters.put(double.class, Lenient::parseDouble);
converters.put(Double.class, Lenient::parseDouble);
converters.put(float.class, Lenient::parseFloat);
converters.put(Float.class, Lenient::parseFloat);
converters.put(BigInteger.class, BigInteger::new);
converters.put(BigDecimal.class, BigDecimal::new);
converters.put(AtomicInteger.class, v -> new AtomicInteger(Integer.parseInt(v)));
converters.put(AtomicLong.class, v -> new AtomicLong(Long.parseLong(v)));
converters.put(AtomicInteger.class, v -> new AtomicInteger(Lenient.parseInt(v)));
converters.put(AtomicLong.class, v -> new AtomicLong(Lenient.parseLong(v)));
converters.put(Duration.class, Duration::parse);
converters.put(Period.class, Period::parse);
converters.put(LocalDateTime.class, LocalDateTime::parse);
Expand All @@ -76,7 +76,7 @@ private DefaultTypeConverterFactory() {
converters.put(OffsetTime.class, OffsetTime::parse);
converters.put(ZonedDateTime.class, ZonedDateTime::parse);
converters.put(Instant.class, v -> Instant.from(OffsetDateTime.parse(v)));
converters.put(Date.class, v -> new Date(Long.parseLong(v)));
converters.put(Date.class, v -> new Date(Lenient.parseLong(v)));
converters.put(Currency.class, Currency::getInstance);
converters.put(URI.class, URI::create);
converters.put(Locale.class, Locale::forLanguageTag);
Expand All @@ -103,4 +103,45 @@ public Optional<TypeConverter<?>> get(Type type, TypeConverter.Registry registry
}
return Optional.empty();
}

/** A collection of lenient number parsers that allow whitespace and trailing 'L' or 'l' in long values */
private static final class Lenient {
private static String maybeTrim(String s) {
// The way these are called, we'll never get a null. In any case, we pass it through, to ensure that
// the exception thrown remains the same as whatever the JDK's parse***() methods throw.
return s != null ? s.trim() : null;
}

private static long parseLong(String s) throws NumberFormatException {
s = maybeTrim(s);
// Also allow trailing 'L' or 'l' in long values
if (s != null) {
if (s.endsWith("L") || s.endsWith("l")) {
s = s.substring(0, s.length() - 1);
}
}

return Long.parseLong(s);
}

private static int parseInt(String s) throws NumberFormatException {
return Integer.parseInt(maybeTrim(s));
}

private static short parseShort(String s) throws NumberFormatException {
return Short.parseShort(maybeTrim(s));
}

private static byte parseByte(String s) throws NumberFormatException {
return Byte.parseByte(maybeTrim(s));
}

private static double parseDouble(String s) throws NumberFormatException {
return Double.parseDouble(maybeTrim(s));
}

private static float parseFloat(String s) throws NumberFormatException {
return Float.parseFloat(maybeTrim(s));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ public void testJavaNumbers() {
assertEquals(BigDecimal.valueOf(Double.MAX_VALUE), decoder.decode(BigDecimal.class, String.valueOf(Double.MAX_VALUE)));
assertEquals(Integer.MAX_VALUE, decoder.decode(AtomicInteger.class, String.valueOf(Integer.MAX_VALUE)).get());
assertEquals(Long.MAX_VALUE, decoder.decode(AtomicLong.class, String.valueOf(Long.MAX_VALUE)).get());

// Verify lenient decoding
assertEquals(123L, decoder.decode(long.class, "123L"));
assertEquals(123L, decoder.decode(long.class, "123l"));
assertEquals(123L, decoder.decode(long.class, "\t\t\n 123L \n\n ")); /// Mixed types of whitespace AND trailing L

assertEquals(123, decoder.decode(int.class, " 123 "));
assertEquals(123.456, decoder.decode(double.class, " 123.456 "));
}

@Test
Expand All @@ -98,7 +106,9 @@ public void testJavaDateTime() {
assertEquals(LocalTime.parse("10:15:30"), decoder.decode(LocalTime.class, "10:15:30"));
assertEquals(Instant.from(OffsetDateTime.parse("2016-08-03T10:15:30+07:00")), decoder.decode(Instant.class, "2016-08-03T10:15:30+07:00"));
Date newDate = new Date();
assertEquals(newDate, decoder.decode(Date.class, String.valueOf(newDate.getTime())));
String encodedDate = String.valueOf(newDate.getTime());
assertEquals(newDate, decoder.decode(Date.class, encodedDate));
assertEquals(newDate, decoder.decode(Date.class, " " + encodedDate + " "), "date decoding should be lenient of whitespace");
}

@Test
Expand All @@ -117,6 +127,8 @@ public void testCollections() {
assertEquals(Collections.emptyList(), decoder.decode(listOfIntegerType, ""));
assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), decoder.decode(listOfIntegerType, "1,2,3,4,5,6"));
assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), decoder.decode(collectionOfLongType, "1,2,3,4,5,6"));
/// We should be lenient with whitespace and trailing L or l
assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), decoder.decode(collectionOfLongType, "1L, 2 , 3l ,4L , 5\t, \n 6 "));
assertEquals(Collections.singleton(2L), decoder.decode(setOfLongType, "2,2,2,2"));
assertEquals(Collections.emptyMap(), decoder.decode(mapofStringToIntegerType, ""));
assertEquals(Collections.singletonMap("key", 12345), decoder.decode(mapofStringToIntegerType, "key=12345"));
Expand All @@ -134,6 +146,8 @@ public void testArrays() {
assertArrayEquals(new long[] {1L, 2L, 3L, 4L, 5L}, decoder.decode(long[].class, "1,2,3,4,5"));
assertArrayEquals(new Long[0], decoder.decode(Long[].class, ""));
assertArrayEquals(new long[0], decoder.decode(long[].class, ""));
/// We should be lenient with whitespace and trailing L or l
assertArrayEquals(new long[] {1L, 2L, 3L, 4L, 5L, 6L}, decoder.decode(long[].class, "1L, 2 , 3l ,4L , 5\t, \n 6 "));
}

enum TestEnumType { FOO, BAR, BAZ }
Expand Down
Loading