From fda5a96ebe201fc0128ca81e72e931b0a23c3487 Mon Sep 17 00:00:00 2001 From: Mark Wardell Date: Sun, 19 Jan 2025 17:43:15 +0400 Subject: [PATCH 1/2] Avoid creating strings when parsing quoted doubles --- .../com/dslplatform/json/NumberConverter.java | 48 ++++++++++++++----- .../dslplatform/json/NumberConverterTest.java | 16 +++++++ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/library/src/main/java/com/dslplatform/json/NumberConverter.java b/library/src/main/java/com/dslplatform/json/NumberConverter.java index eaf8ff03..7dfa919a 100644 --- a/library/src/main/java/com/dslplatform/json/NumberConverter.java +++ b/library/src/main/java/com/dslplatform/json/NumberConverter.java @@ -382,7 +382,7 @@ static NumberInfo readLongNumber(final JsonReader reader, final int start) throw return new NumberInfo(result, len); } - public static double deserializeDouble(final JsonReader reader) throws IOException { + public static double deserializeDouble0(final JsonReader reader) throws IOException { if (reader.last() == '"') { final int position = reader.getCurrentIndex(); final char[] buf = reader.readSimpleQuote(); @@ -393,30 +393,54 @@ public static double deserializeDouble(final JsonReader reader) throws IOExcepti final byte[] buf = reader.buffer; final byte ch = buf[start]; if (ch == '-') { - return -parseDouble(buf, reader, start, end, 1); + return -parseDouble(buf, reader, start, end, 1, false); } - return parseDouble(buf, reader, start, end, 0); + return parseDouble(buf, reader, start, end, 0, false); } - private static double parseDouble(final byte[] buf, final JsonReader reader, final int start, final int end, final int offset) throws IOException { + public static double deserializeDouble(final JsonReader reader) throws IOException { + final boolean withQuotes = reader.last() == '"'; + final int start, end; + if (withQuotes) { + final char[] tmp = reader.readSimpleQuote(); + start = reader.getTokenStart(); + end = reader.getCurrentIndex() - 1; + } + else { + start = reader.scanNumber(); + end = reader.getCurrentIndex(); + } + final byte[] buf = reader.buffer; + final byte ch = buf[start]; + if (ch == '-') { + return -parseDouble(buf, reader, start, end, 1, withQuotes); + } + return parseDouble(buf, reader, start, end, 0, withQuotes); + } + + private static double parseDouble(final byte[] buf, final JsonReader reader, final int start, final int end, final int offset, final boolean withQuotes) throws IOException { if (end - start - offset > reader.doubleLengthLimit) { if (end == reader.length()) { final NumberInfo tmp = readLongNumber(reader, start + offset); - return parseDoubleGeneric(tmp.buffer, tmp.length, reader, false); + return parseDoubleGeneric(tmp.buffer, tmp.length, reader, withQuotes); } - return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, false); + return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, withQuotes); } long value = 0; byte ch = ' '; int i = start + offset; - final boolean leadingZero = buf[start + offset] == 48; + final boolean leadingZero = buf[i] == 48; + // Add support for NaN and Infinity now that this method handles quoted values + if (buf[i] == 'N' || buf[i] == 'I') { + return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, withQuotes); + } for (; i < end; i++) { ch = buf[i]; if (ch == '.' || ch == 'e' || ch == 'E') break; final int ind = buf[i] - 48; if (ind < 0 || ind > 9) { if (leadingZero && i > start + offset + 1) { - numberException(reader, start, end, "Leading zero is not allowed"); + numberException(reader, start, end + (withQuotes ? 2 : 0), "Leading zero is not allowed"); } if (i > start + offset && reader.allWhitespace(i, end)) return value; numberException(reader, start, end, "Unknown digit", (char)ch); @@ -424,7 +448,7 @@ private static double parseDouble(final byte[] buf, final JsonReader reader, fin value = (value << 3) + (value << 1) + ind; } if (i == start + offset) numberException(reader, start, end, "Digit not found"); - else if (leadingZero && ch != '.' && i > start + offset + 1) numberException(reader, start, end, "Leading zero is not allowed"); + else if (leadingZero && ch != '.' && i > start + offset + 1) numberException(reader, start, end + (withQuotes ? 2 : 0), "Leading zero is not allowed"); else if (i == end) return value; else if (ch == '.') { i++; @@ -438,7 +462,7 @@ else if (ch == '.') { maxLen = i + 15; ch = buf[i]; if (ch == '0' && end > maxLen) { - return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, false); + return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, withQuotes); } else if (ch < '8') { preciseDividor = 1e14; expDiff = -1; @@ -477,7 +501,7 @@ else if (ch == 'e' || ch == 'E') { return doubleExponent(reader, value, i - decPos,0, buf, start, end, offset, i); } if (reader.doublePrecision == JsonReader.DoublePrecision.HIGH) { - return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, false); + return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, withQuotes); } int decimals = 0; final int decLimit = start + offset + 18 < end ? start + offset + 18 : end; @@ -1755,4 +1779,4 @@ public static ArrayList deserializeDecimalNullableCollection(final J public static void deserializeDecimalNullableCollection(final JsonReader reader, final Collection res) throws IOException { reader.deserializeNullableCollection(DECIMAL_READER, res); } -} \ No newline at end of file +} diff --git a/library/src/test/java/com/dslplatform/json/NumberConverterTest.java b/library/src/test/java/com/dslplatform/json/NumberConverterTest.java index 2980f478..3349ac72 100644 --- a/library/src/test/java/com/dslplatform/json/NumberConverterTest.java +++ b/library/src/test/java/com/dslplatform/json/NumberConverterTest.java @@ -1563,4 +1563,20 @@ public void testNumberDeserializationWithNewStreamWhenAtBoundaryAndMore() throws Assert.assertEquals(expected, actual); } + + @Test + public void quotedDoubles() throws IOException { + // setup + final JsonWriter sw = new JsonWriter(null); + final String[] inputs = { "\"104923.87000004\"", "\"0.00230002\"","\"-104923.87000004\"", "\"-0.00230002\"" }; + final JsonReader jr = dslJson.newReader(new byte[0]); + for (String inp : inputs) { + double expected = Double.parseDouble(inp.substring(1, inp.length() - 1)); + byte[] bytes = inp.getBytes("UTF-8"); + jr.process(bytes, bytes.length); + jr.read(); + double number = NumberConverter.deserializeDouble(jr); + Assert.assertEquals(expected, number, 0); + } + } } From 716b0fc07a1afaa3724baee2303d8069672aa3c9 Mon Sep 17 00:00:00 2001 From: Mark Wardell Date: Sun, 19 Jan 2025 17:47:22 +0400 Subject: [PATCH 2/2] Remove unused method --- .../com/dslplatform/json/NumberConverter.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/library/src/main/java/com/dslplatform/json/NumberConverter.java b/library/src/main/java/com/dslplatform/json/NumberConverter.java index 7dfa919a..496bc138 100644 --- a/library/src/main/java/com/dslplatform/json/NumberConverter.java +++ b/library/src/main/java/com/dslplatform/json/NumberConverter.java @@ -382,22 +382,6 @@ static NumberInfo readLongNumber(final JsonReader reader, final int start) throw return new NumberInfo(result, len); } - public static double deserializeDouble0(final JsonReader reader) throws IOException { - if (reader.last() == '"') { - final int position = reader.getCurrentIndex(); - final char[] buf = reader.readSimpleQuote(); - return parseDoubleGeneric(buf, reader.getCurrentIndex() - position - 1, reader, true); - } - final int start = reader.scanNumber(); - final int end = reader.getCurrentIndex(); - final byte[] buf = reader.buffer; - final byte ch = buf[start]; - if (ch == '-') { - return -parseDouble(buf, reader, start, end, 1, false); - } - return parseDouble(buf, reader, start, end, 0, false); - } - public static double deserializeDouble(final JsonReader reader) throws IOException { final boolean withQuotes = reader.last() == '"'; final int start, end;