From cc730cc860e7f8aa1b85a1298f49cf431c9d72f9 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 26 Dec 2013 22:04:56 +1300 Subject: [PATCH] -Improved number parsing error message --- .../JsonTextReaderTest.cs | 96 ++++++++++++++++--- .../Properties/AssemblyInfo.cs | 2 +- Src/Newtonsoft.Json.sln.DotSettings | 1 + Src/Newtonsoft.Json/JsonTextReader.cs | 48 +++++++--- .../Properties/AssemblyInfo.cs | 2 +- Src/Newtonsoft.Json/Utilities/ConvertUtils.cs | 29 +++--- 6 files changed, 136 insertions(+), 42 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index f2d51d0e..7b6b6df8 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -799,8 +799,8 @@ Parameter name: reader", for (int j = 1; j < 1000; j++) { long total = j + i; - ExceptionAssert.Throws( - "Arithmetic operation resulted in an overflow.", + ExceptionAssert.Throws( + "JSON integer " + total + " is too large or small for an Int32. Path '', line 1, position 10.", () => { reader = new JsonTextReader(new StringReader(total.ToString(CultureInfo.InvariantCulture))); @@ -822,8 +822,8 @@ Parameter name: reader", for (int j = 1; j < 1000; j++) { long total = -j + i; - ExceptionAssert.Throws( - "Arithmetic operation resulted in an overflow.", + ExceptionAssert.Throws( + "JSON integer " + total + " is too large or small for an Int32. Path '', line 1, position 11.", () => { reader = new JsonTextReader(new StringReader(total.ToString(CultureInfo.InvariantCulture))); @@ -1230,22 +1230,96 @@ bye", reader.Value); Assert.AreEqual(int.MinValue, reader.ReadAsInt32()); reader = new JsonTextReader(new StringReader(long.MaxValue.ToString())); - ExceptionAssert.Throws("Arithmetic operation resulted in an overflow.", () => reader.ReadAsInt32()); + ExceptionAssert.Throws("JSON integer 9223372036854775807 is too large or small for an Int32. Path '', line 1, position 19.", () => reader.ReadAsInt32()); reader = new JsonTextReader(new StringReader("9999999999999999999999999999999999999999999999999999999999999999999999999999asdasdasd")); - ExceptionAssert.Throws("Input string was not in a correct format.", () => reader.ReadAsInt32()); + ExceptionAssert.Throws("Input string '9999999999999999999999999999999999999999999999999999999999999999999999999999a' is not a valid integer. Path '', line 1, position 77.", () => reader.ReadAsInt32()); reader = new JsonTextReader(new StringReader("1E-06")); - ExceptionAssert.Throws("Input string was not in a correct format.", () => reader.ReadAsInt32()); + ExceptionAssert.Throws("Input string '1E-06' is not a valid integer. Path '', line 1, position 5.", () => reader.ReadAsInt32()); reader = new JsonTextReader(new StringReader("1.1")); - ExceptionAssert.Throws("Input string was not in a correct format.", () => reader.ReadAsInt32()); + ExceptionAssert.Throws("Input string '1.1' is not a valid integer. Path '', line 1, position 3.", () => reader.ReadAsInt32()); reader = new JsonTextReader(new StringReader("")); Assert.AreEqual(null, reader.ReadAsInt32()); reader = new JsonTextReader(new StringReader("-")); - ExceptionAssert.Throws("Input string was not in a correct format.", () => reader.ReadAsInt32()); + ExceptionAssert.Throws("Input string '-' is not a valid integer. Path '', line 1, position 1.", () => reader.ReadAsInt32()); + } + + [Test] + public void ParseDecimals() + { + JsonTextReader reader = null; + + reader = new JsonTextReader(new StringReader("1.1")); + Assert.AreEqual(1.1m, reader.ReadAsDecimal()); + + reader = new JsonTextReader(new StringReader("-1.1")); + Assert.AreEqual(-1.1m, reader.ReadAsDecimal()); + + reader = new JsonTextReader(new StringReader("0.0")); + Assert.AreEqual(0.0m, reader.ReadAsDecimal()); + + reader = new JsonTextReader(new StringReader("-0.0")); + Assert.AreEqual(0, reader.ReadAsDecimal()); + + reader = new JsonTextReader(new StringReader("9999999999999999999999999999999999999999999999999999999999999999999999999999asdasdasd")); + ExceptionAssert.Throws("Input string '9999999999999999999999999999999999999999999999999999999999999999999999999999a' is not a valid decimal. Path '', line 1, position 77.", () => reader.ReadAsDecimal()); + + reader = new JsonTextReader(new StringReader("9999999999999999999999999999999999999999999999999999999999999999999999999999asdasdasd")); + reader.FloatParseHandling = Json.FloatParseHandling.Decimal; + ExceptionAssert.Throws("Input string '9999999999999999999999999999999999999999999999999999999999999999999999999999a' is not a valid decimal. Path '', line 1, position 77.", () => reader.Read()); + + reader = new JsonTextReader(new StringReader("1E-06")); + Assert.AreEqual(0.000001m, reader.ReadAsDecimal()); + + reader = new JsonTextReader(new StringReader("")); + Assert.AreEqual(null, reader.ReadAsDecimal()); + + reader = new JsonTextReader(new StringReader("-")); + ExceptionAssert.Throws("Input string '-' is not a valid decimal. Path '', line 1, position 1.", () => reader.ReadAsDecimal()); + } + + [Test] + public void ParseDoubles() + { + JsonTextReader reader = null; + + reader = new JsonTextReader(new StringReader("1.1")); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(typeof(double), reader.ValueType); + Assert.AreEqual(1.1d, reader.Value); + + reader = new JsonTextReader(new StringReader("-1.1")); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(typeof(double), reader.ValueType); + Assert.AreEqual(-1.1d, reader.Value); + + reader = new JsonTextReader(new StringReader("0.0")); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(typeof(double), reader.ValueType); + Assert.AreEqual(0.0d, reader.Value); + + reader = new JsonTextReader(new StringReader("-0.0")); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(typeof(double), reader.ValueType); + Assert.AreEqual(-0.0m, reader.Value); + + reader = new JsonTextReader(new StringReader("9999999999999999999999999999999999999999999999999999999999999999999999999999asdasdasd")); + ExceptionAssert.Throws("Input string '9999999999999999999999999999999999999999999999999999999999999999999999999999a' is not a valid number. Path '', line 1, position 77.", () => reader.Read()); + + reader = new JsonTextReader(new StringReader("1E-06")); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(typeof(double), reader.ValueType); + Assert.AreEqual(0.000001m, reader.Value); + + reader = new JsonTextReader(new StringReader("")); + Assert.IsFalse(reader.Read()); + + reader = new JsonTextReader(new StringReader("-")); + ExceptionAssert.Throws("Input string '-' is not a valid number. Path '', line 1, position 1.", () => reader.Read()); } [Test] @@ -1975,8 +2049,8 @@ bye", reader.Value); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); - ExceptionAssert.Throws( - "Input string was not in a correct format.", + ExceptionAssert.Throws( + "Input string '1.1' is not a valid integer. Path 'Name', line 1, position 12.", () => { reader.ReadAsInt32(); }); } diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index fc0b2a81..ffa2caa4 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.8.16823")] +[assembly: AssemblyFileVersion("5.0.8.16826")] diff --git a/Src/Newtonsoft.Json.sln.DotSettings b/Src/Newtonsoft.Json.sln.DotSettings index 6a89a056..c97ec47f 100644 --- a/Src/Newtonsoft.Json.sln.DotSettings +++ b/Src/Newtonsoft.Json.sln.DotSettings @@ -2,4 +2,5 @@ False True False + True \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 3e006808..c1acd2e1 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -1195,7 +1195,14 @@ namespace Newtonsoft.Json } else { - numberValue = ConvertUtils.Int32Parse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length); + int value; + ParseResult parseResult = ConvertUtils.Int32TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out value); + if (parseResult == ParseResult.Success) + numberValue = value; + else if (parseResult == ParseResult.Overflow) + throw JsonReaderException.Create(this, "JSON integer {0} is too large or small for an Int32.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + else + throw JsonReaderException.Create(this, "Input string '{0}' is not a valid integer.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); } numberType = JsonToken.Integer; @@ -1222,7 +1229,11 @@ namespace Newtonsoft.Json { string number = _stringReference.ToString(); - numberValue = decimal.Parse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture); + decimal value; + if (decimal.TryParse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out value)) + numberValue = value; + else + throw JsonReaderException.Create(this, "Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); } numberType = JsonToken.Float; @@ -1253,17 +1264,6 @@ namespace Newtonsoft.Json numberValue = value; numberType = JsonToken.Integer; } - else if (parseResult == ParseResult.Invalid) - { - string number = _stringReference.ToString(); - - if (_floatParseHandling == FloatParseHandling.Decimal) - numberValue = decimal.Parse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture); - else - numberValue = Convert.ToDouble(number, CultureInfo.InvariantCulture); - - numberType = JsonToken.Float; - } else if (parseResult == ParseResult.Overflow) { #if !(NET20 || NET35 || PORTABLE40 || PORTABLE) @@ -1271,13 +1271,31 @@ namespace Newtonsoft.Json numberValue = BigInteger.Parse(number, CultureInfo.InvariantCulture); numberType = JsonToken.Integer; #else - // todo - validate number was a valid integer to make sure overflow was the reason for failure throw JsonReaderException.Create(this, "JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); #endif } else { - throw JsonReaderException.Create(this, "Unknown error parsing integer."); + string number = _stringReference.ToString(); + + if (_floatParseHandling == FloatParseHandling.Decimal) + { + decimal d; + if (decimal.TryParse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out d)) + numberValue = d; + else + throw JsonReaderException.Create(this, "Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, number)); + } + else + { + double d; + if (double.TryParse(number, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, out d)) + numberValue = d; + else + throw JsonReaderException.Create(this, "Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, number)); + } + + numberType = JsonToken.Float; } } } diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index d3d2b37e..30769750 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -91,5 +91,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.8.16823")] +[assembly: AssemblyFileVersion("5.0.8.16826")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs index a33da5bb..d87e904c 100644 --- a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs @@ -565,10 +565,12 @@ namespace Newtonsoft.Json.Utilities } } - public static int Int32Parse(char[] chars, int start, int length) + public static ParseResult Int32TryParse(char[] chars, int start, int length, out int value) { + value = 0; + if (length == 0) - throw new FormatException("Input string was not in a correct format."); + return ParseResult.Invalid; bool isNegative = (chars[start] == '-'); @@ -576,13 +578,12 @@ namespace Newtonsoft.Json.Utilities { // text just a negative sign if (length == 1) - throw new FormatException("Input string was not in a correct format."); + return ParseResult.Invalid; start++; length--; } - int result = 0; int end = start + length; for (int i = start; i < end; i++) @@ -590,12 +591,12 @@ namespace Newtonsoft.Json.Utilities int c = chars[i] - '0'; if (c < 0 || c > 9) - throw new FormatException("Input string was not in a correct format."); + return ParseResult.Invalid; - int newValue = (10 * result) - c; + int newValue = (10 * value) - c; // overflow has caused the number to loop around - if (newValue > result) + if (newValue > value) { i++; @@ -606,13 +607,13 @@ namespace Newtonsoft.Json.Utilities c = chars[i] - '0'; if (c < 0 || c > 9) - throw new FormatException("Input string was not in a correct format."); + return ParseResult.Invalid; } - throw new OverflowException(); + return ParseResult.Overflow; } - result = newValue; + value = newValue; } // go from negative to positive to avoids overflow @@ -620,13 +621,13 @@ namespace Newtonsoft.Json.Utilities if (!isNegative) { // negative integer can be one bigger than positive - if (result == int.MinValue) - throw new OverflowException(); + if (value == int.MinValue) + return ParseResult.Overflow; - result = -result; + value = -value; } - return result; + return ParseResult.Success; } public static ParseResult Int64TryParse(char[] chars, int start, int length, out long value)