diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index b382ab7f..899c072f 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -2771,6 +2771,35 @@ new Date()")); Assert.AreEqual(JsonToken.EndConstructor, reader.TokenType); } + [Test] + public void ReadAsDouble_Null() + { + JsonTextReader reader = new JsonTextReader(new StringReader("null")); + Assert.AreEqual(null, reader.ReadAsDouble()); + } + + [Test] + public void ReadAsDouble_Success() + { + JsonTextReader reader = new JsonTextReader(new StringReader("'12.34'")); + Assert.AreEqual(12.34d, reader.ReadAsDouble()); + } + + [Test] + public void ReadAsDouble_Failure() + { + JsonTextReader reader = new JsonTextReader(new StringReader("['Trump',1]")); + + Assert.IsTrue(reader.Read()); + + ExceptionAssert.Throws( + () => { reader.ReadAsDouble(); }, + "Could not convert string to double: Trump. Path '[0]', line 1, position 8."); + + Assert.AreEqual(1d, reader.ReadAsDouble()); + Assert.IsTrue(reader.Read()); + } + [Test] public void ParseConstructorWithBadCharacter() { @@ -2905,7 +2934,7 @@ new Date()")); ExceptionAssert.Throws(() => { reader.ReadAsBytes(); - }, "Unexpected character encountered while parsing value: ,. Path '[1]', line 1, position 4."); + }, "Unexpected character encountered while parsing value: ,. Path '[1]', line 1, position 5."); CollectionAssert.AreEquivalent(new byte[0], reader.ReadAsBytes()); Assert.IsTrue(reader.Read()); @@ -2967,10 +2996,11 @@ new Date()")); Assert.IsTrue(reader.Read()); } +#if !NET20 [Test] public void ReadValue_EmptyString_Position() { - string json = @"['','','','','','']"; + string json = @"['','','','','','','']"; JsonTextReader reader = new JsonTextReader(new StringReader(json)); @@ -2987,6 +3017,8 @@ new Date()")); Assert.AreEqual("[4]", reader.Path); reader.ReadAsBytes(); Assert.AreEqual("[5]", reader.Path); + reader.ReadAsDouble(); + Assert.AreEqual("[6]", reader.Path); Assert.IsNull(reader.ReadAsString()); Assert.AreEqual(JsonToken.EndArray, reader.TokenType); @@ -2997,6 +3029,7 @@ new Date()")); Assert.IsNull(reader.ReadAsBytes()); Assert.AreEqual(JsonToken.None, reader.TokenType); } +#endif [Test] public void ReadValueComments() @@ -3783,7 +3816,6 @@ null//comment var serverStartedEvent = new ManualResetEvent(false); var clientReceivedEvent = new ManualResetEvent(false); - #region server ThreadPool.QueueUserWorkItem(work => { var server = new TcpListener(IPAddress.Parse("0.0.0.0"), port); @@ -3802,7 +3834,6 @@ null//comment clientReceivedEvent.WaitOne(); }); - #endregion serverStartedEvent.WaitOne(); @@ -3838,6 +3869,7 @@ null//comment Console.WriteLine("Done"); } #endif + [Test] public void DisposeSupressesFinalization() { diff --git a/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs b/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs index 484b59c0..f6031120 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs @@ -726,5 +726,201 @@ namespace Newtonsoft.Json.Tests.Linq Assert.IsFalse(reader.Read()); Assert.AreEqual("[0]", reader.Path); } + + [Test] + public void ReadAsDouble_InvalidToken() + { + JArray a = new JArray + { + 1, 2 + }; + + JTokenReader reader = new JTokenReader(a); + + ExceptionAssert.Throws( + () => { reader.ReadAsDouble(); }, + "Error reading double. Unexpected token: StartArray. Path ''."); + } + + [Test] + public void ReadAsDateTime_InvalidToken() + { + JArray a = new JArray + { + 1, 2 + }; + + JTokenReader reader = new JTokenReader(a); + + ExceptionAssert.Throws( + () => { reader.ReadAsDateTime(); }, + "Error reading date. Unexpected token: StartArray. Path ''."); + } + +#if !NET20 + [Test] + public void ReadAsDateTimeOffset_InvalidToken() + { + JArray a = new JArray + { + 1, 2 + }; + + JTokenReader reader = new JTokenReader(a); + + ExceptionAssert.Throws( + () => { reader.ReadAsDateTimeOffset(); }, + "Error reading date. Unexpected token: StartArray. Path ''."); + } + + [Test] + public void ReadAsDateTimeOffset_DateTime() + { + JValue v = new JValue(new DateTime(2001, 12, 12, 12, 12, 12, DateTimeKind.Utc)); + + JTokenReader reader = new JTokenReader(v); + + Assert.AreEqual(new DateTimeOffset(2001, 12, 12, 12, 12, 12, TimeSpan.Zero), reader.ReadAsDateTimeOffset()); + } + + [Test] + public void ReadAsDateTimeOffset_String() + { + JValue v = new JValue("2012-01-24T03:50Z"); + + JTokenReader reader = new JTokenReader(v); + + Assert.AreEqual(new DateTimeOffset(2012, 1, 24, 3, 50, 0, TimeSpan.Zero), reader.ReadAsDateTimeOffset()); + } + + [Test] + public void ReadAsDateTime_DateTimeOffset() + { + JValue v = new JValue(new DateTimeOffset(2012, 1, 24, 3, 50, 0, TimeSpan.Zero)); + + JTokenReader reader = new JTokenReader(v); + + Assert.AreEqual(new DateTime(2012, 1, 24, 3, 50, 0, DateTimeKind.Utc), reader.ReadAsDateTime()); + } +#endif + + [Test] + public void ReadAsDateTime_String() + { + JValue v = new JValue("2012-01-24T03:50Z"); + + JTokenReader reader = new JTokenReader(v); + + Assert.AreEqual(new DateTime(2012, 1, 24, 3, 50, 0, DateTimeKind.Utc), reader.ReadAsDateTime()); + } + + [Test] + public void ReadAsDouble_String_Success() + { + JValue s = JValue.CreateString("123.4"); + + JTokenReader reader = new JTokenReader(s); + + Assert.AreEqual(123.4d, reader.ReadAsDouble()); + } + + [Test] + public void ReadAsDouble_Null_Success() + { + JValue n = JValue.CreateNull(); + + JTokenReader reader = new JTokenReader(n); + + Assert.AreEqual(null, reader.ReadAsDouble()); + } + + [Test] + public void ReadAsDouble_Integer_Success() + { + JValue n = new JValue(1); + + JTokenReader reader = new JTokenReader(n); + + Assert.AreEqual(1d, reader.ReadAsDouble()); + } + + [Test] + public void ReadAsDateTime_Null_Success() + { + JValue n = JValue.CreateNull(); + + JTokenReader reader = new JTokenReader(n); + + Assert.AreEqual(null, reader.ReadAsDateTime()); + } + +#if !NET20 + [Test] + public void ReadAsDateTimeOffset_Null_Success() + { + JValue n = JValue.CreateNull(); + + JTokenReader reader = new JTokenReader(n); + + Assert.AreEqual(null, reader.ReadAsDateTimeOffset()); + } +#endif + + [Test] + public void ReadAsString_Integer_Success() + { + JValue n = new JValue(1); + + JTokenReader reader = new JTokenReader(n); + + Assert.AreEqual("1", reader.ReadAsString()); + } + + [Test] + public void ReadAsString_Guid_Success() + { + JValue n = new JValue(new Uri("http://www.test.com")); + + JTokenReader reader = new JTokenReader(n); + + Assert.AreEqual("http://www.test.com", reader.ReadAsString()); + } + + [Test] + public void ReadAsBytes_Integer_Success() + { + JValue n = JValue.CreateNull(); + + JTokenReader reader = new JTokenReader(n); + + Assert.AreEqual(null, reader.ReadAsBytes()); + } + + [Test] + public void ReadAsBytes_Array() + { + JArray a = new JArray + { + 1, 2 + }; + + JTokenReader reader = new JTokenReader(a); + + byte[] bytes = reader.ReadAsBytes(); + + Assert.AreEqual(2, bytes.Length); + Assert.AreEqual(1, bytes[0]); + Assert.AreEqual(2, bytes[1]); + } + + [Test] + public void ReadAsBytes_Null() + { + JValue n = JValue.CreateNull(); + + JTokenReader reader = new JTokenReader(n); + + Assert.AreEqual(null, reader.ReadAsBytes()); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index e10cfef8..278bb5a8 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -3716,9 +3716,9 @@ Path '', line 1, position 1."); { string json = @"[]"; - ExceptionAssert.Throws(() => { JsonConvert.DeserializeObject(json); }, @"Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Double' because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly. -To fix this error either change the JSON to a JSON primitive value (e.g. string, number, boolean, null) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array. -Path '', line 1, position 1."); + ExceptionAssert.Throws( + () => { JsonConvert.DeserializeObject(json); }, + @"Unexpected character encountered while parsing value: [. Path '', line 1, position 1."); } #if !(NET35 || NET20 || PORTABLE40) diff --git a/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs index d61736c5..2353fbd1 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs @@ -532,7 +532,7 @@ namespace Newtonsoft.Json.Tests.Serialization public class ItemWithTypedPayload { - public double Payload { get; set; } + public float Payload { get; set; } } [Test] @@ -540,19 +540,19 @@ namespace Newtonsoft.Json.Tests.Serialization { ItemWithTypedPayload actual = JsonConvert.DeserializeObject(@"{ ""Payload"": { - ""$type"": ""System.Double, mscorlib"", + ""$type"": ""System.Single, mscorlib"", ""$value"": ""5"" } }", new JsonSerializerSettings()); - Assert.AreEqual(5d, actual.Payload); + Assert.AreEqual(5f, actual.Payload); ExceptionAssert.Throws(() => { JsonConvert.DeserializeObject(@"{ ""Payload"": { - ""$type"": ""System.Double, mscorlib"", + ""$type"": ""System.Single, mscorlib"", ""$value"": ""5"" } }", @@ -560,7 +560,7 @@ namespace Newtonsoft.Json.Tests.Serialization { MetadataPropertyHandling = MetadataPropertyHandling.Ignore }); - }, @"Cannot deserialize the current JSON object (e.g. {""name"":""value""}) into type 'System.Double' because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly. + }, @"Cannot deserialize the current JSON object (e.g. {""name"":""value""}) into type 'System.Single' because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly. To fix this error either change the JSON to a JSON primitive value (e.g. string, number, boolean, null) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path 'Payload.$type', line 3, position 12."); } diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index 761ec563..d2a0ec80 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -484,6 +484,10 @@ namespace Newtonsoft.Json { s = ((IFormattable)Value).ToString(null, Culture); } + else if (Value is Uri) + { + s = ((Uri)Value).OriginalString; + } else { s = Value.ToString(); @@ -590,6 +594,56 @@ namespace Newtonsoft.Json } } + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public virtual double? ReadAsDouble() + { + JsonToken t = GetContentToken(); + + switch (t) + { + case JsonToken.None: + case JsonToken.Null: + case JsonToken.EndArray: + return null; + case JsonToken.Integer: + case JsonToken.Float: + if (!(Value is double)) + { + SetToken(JsonToken.Float, Convert.ToDouble(Value, CultureInfo.InvariantCulture), false); + } + + return (double) Value; + case JsonToken.String: + return ReadDoubleString((string) Value); + } + + throw JsonReaderException.Create(this, "Error reading double. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); + } + + internal double? ReadDoubleString(string s) + { + if (string.IsNullOrEmpty(s)) + { + SetToken(JsonToken.Null, null, false); + return null; + } + + double d; + if (double.TryParse(s, NumberStyles.Float, Culture, out d)) + { + SetToken(JsonToken.Float, d, false); + return d; + } + else + { + SetToken(JsonToken.String, s, false); + throw JsonReaderException.Create(this, "Could not convert string to double: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); + } + } + /// /// Reads the next JSON token from the stream as a . /// @@ -653,6 +707,13 @@ namespace Newtonsoft.Json case JsonToken.EndArray: return null; case JsonToken.Date: +#if !NET20 + if (Value is DateTimeOffset) + { + SetToken(JsonToken.Date, ((DateTimeOffset)Value).DateTime, false); + } +#endif + return (DateTime)Value; case JsonToken.String: string s = (string)Value; diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index f64c8d85..14102dcf 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -43,8 +43,9 @@ namespace Newtonsoft.Json ReadAsDecimal, ReadAsDateTime, #if !NET20 - ReadAsDateTimeOffset + ReadAsDateTimeOffset, #endif + ReadAsDouble } /// @@ -702,6 +703,8 @@ namespace Newtonsoft.Json return ReadInt32String(_stringReference.ToString()); case ReadType.ReadAsDecimal: return ReadDecimalString(_stringReference.ToString()); + case ReadType.ReadAsDouble: + return ReadDoubleString(_stringReference.ToString()); default: throw new ArgumentOutOfRangeException(nameof(readType)); } @@ -765,8 +768,8 @@ namespace Newtonsoft.Json // eat break; + } } - } case State.Finished: ReadFinished(); SetToken(JsonToken.None); @@ -796,6 +799,15 @@ namespace Newtonsoft.Json return (decimal?)ReadNumberValue(ReadType.ReadAsDecimal); } + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override double? ReadAsDouble() + { + return (double?)ReadNumberValue(ReadType.ReadAsDouble); + } + private void HandleNull() { if (EnsureChars(1, true)) @@ -1733,6 +1745,46 @@ namespace Newtonsoft.Json numberType = JsonToken.Float; } + else if (readType == ReadType.ReadAsDouble) + { + if (singleDigit) + { + // digit char values start at 48 + numberValue = (double)firstChar - 48; + } + else if (nonBase10) + { + string number = _stringReference.ToString(); + + try + { + // double.Parse doesn't support parsing hexadecimal values + long integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); + + numberValue = Convert.ToDouble(integer); + } + catch (Exception ex) + { + throw JsonReaderException.Create(this, "Input string '{0}' is not a valid double.".FormatWith(CultureInfo.InvariantCulture, number), ex); + } + } + else + { + string number = _stringReference.ToString(); + + double value; + if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out value)) + { + numberValue = value; + } + else + { + throw JsonReaderException.Create(this, "Input string '{0}' is not a valid double.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } + } + + numberType = JsonToken.Float; + } else { if (singleDigit) diff --git a/Src/Newtonsoft.Json/JsonValidatingReader.cs b/Src/Newtonsoft.Json/JsonValidatingReader.cs index 7d766783..bd820c4e 100644 --- a/Src/Newtonsoft.Json/JsonValidatingReader.cs +++ b/Src/Newtonsoft.Json/JsonValidatingReader.cs @@ -429,6 +429,18 @@ namespace Newtonsoft.Json return d; } + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . + public override double? ReadAsDouble() + { + double? d = _reader.ReadAsDouble(); + + ValidateCurrentToken(); + return d; + } + /// /// Reads the next JSON token from the stream as a . /// diff --git a/Src/Newtonsoft.Json/Linq/JTokenReader.cs b/Src/Newtonsoft.Json/Linq/JTokenReader.cs index ae9e6587..c3f26736 100644 --- a/Src/Newtonsoft.Json/Linq/JTokenReader.cs +++ b/Src/Newtonsoft.Json/Linq/JTokenReader.cs @@ -225,7 +225,15 @@ namespace Newtonsoft.Json.Linq SetToken(JsonToken.String, SafeToString(((JValue)token).Value)); break; case JTokenType.Uri: - SetToken(JsonToken.String, SafeToString(((JValue)token).Value)); + object v = ((JValue)token).Value; + if (v is Uri) + { + SetToken(JsonToken.String, ((Uri)v).OriginalString); + } + else + { + SetToken(JsonToken.String, SafeToString(v)); + } break; case JTokenType.TimeSpan: SetToken(JsonToken.String, SafeToString(((JValue)token).Value)); diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index c36b500e..c9de554b 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -347,6 +347,10 @@ namespace Newtonsoft.Json.Serialization InternalReadType = ReadType.ReadAsDateTimeOffset; } #endif + else if (NonNullableUnderlyingType == typeof(double)) + { + InternalReadType = ReadType.ReadAsDouble; + } else { InternalReadType = ReadType.Read; diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index a503d23a..89a82ef7 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -2210,6 +2210,9 @@ namespace Newtonsoft.Json.Serialization case ReadType.ReadAsDecimal: reader.ReadAsDecimal(); break; + case ReadType.ReadAsDouble: + reader.ReadAsDouble(); + break; case ReadType.ReadAsBytes: reader.ReadAsBytes(); break; diff --git a/Src/Newtonsoft.Json/Serialization/TraceJsonReader.cs b/Src/Newtonsoft.Json/Serialization/TraceJsonReader.cs index e363540e..d9ea487b 100644 --- a/Src/Newtonsoft.Json/Serialization/TraceJsonReader.cs +++ b/Src/Newtonsoft.Json/Serialization/TraceJsonReader.cs @@ -64,6 +64,13 @@ namespace Newtonsoft.Json.Serialization return value; } + public override double? ReadAsDouble() + { + var value = _innerReader.ReadAsDouble(); + _textWriter.WriteToken(_innerReader, false, false, true); + return value; + } + public override DateTime? ReadAsDateTime() { var value = _innerReader.ReadAsDateTime();