From 3335ae4e209681166ce6b210baae6be3db0a3e75 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 28 Jun 2014 00:42:41 +1200 Subject: [PATCH] -Fixed property after empty array JSONPath queries -Fixed error handling when last value in array or object fails --- .../JsonTextReaderTest.cs | 152 ++++++++++++++++++ .../Linq/JsonPath/JPathExecuteTests.cs | 16 ++ .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/JsonSerializerTest.cs | 36 +++++ Src/Newtonsoft.Json/JsonReader.cs | 25 +-- Src/Newtonsoft.Json/JsonTextReader.cs | 35 ++-- .../Properties/AssemblyInfo.cs | 2 +- .../Utilities/DateTimeUtils.cs | 40 ++--- .../Utilities/ReflectionUtils.cs | 20 +-- 9 files changed, 265 insertions(+), 63 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index 94f9794b..22cff327 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Newtonsoft.Json.Linq; #if !(NET20 || NET35 || PORTABLE40 || PORTABLE) using System.Numerics; #endif @@ -47,6 +48,157 @@ namespace Newtonsoft.Json.Tests [TestFixture] public class JsonTextReaderTest : TestFixtureBase { +#if !(NET20 || NET35 || PORTABLE40 || PORTABLE) + [Test] + public void ReadBigInteger() + { + string json = @"{ + ParentId: 1, + ChildId: 333333333333333333333333333333333333333, +}"; + + JsonTextReader jsonTextReader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.StartObject, jsonTextReader.TokenType); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.PropertyName, jsonTextReader.TokenType); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.Integer, jsonTextReader.TokenType); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.PropertyName, jsonTextReader.TokenType); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.Integer, jsonTextReader.TokenType); + Assert.AreEqual(typeof(BigInteger), jsonTextReader.ValueType); + Assert.AreEqual(BigInteger.Parse("333333333333333333333333333333333333333"), jsonTextReader.Value); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.EndObject, jsonTextReader.TokenType); + + Assert.IsFalse(jsonTextReader.Read()); + + JObject o = JObject.Parse(json); + var i = (BigInteger)((JValue)o["ChildId"]).Value; + Assert.AreEqual(BigInteger.Parse("333333333333333333333333333333333333333"), i); + } +#endif + + [Test] + public void ReadIntegerWithError() + { + string json = @"{ + ChildId: 333333333333333333333333333333333333333 +}"; + + JsonTextReader jsonTextReader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.StartObject, jsonTextReader.TokenType); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.PropertyName, jsonTextReader.TokenType); + + ExceptionAssert.Throws( + "JSON integer 333333333333333333333333333333333333333 is too large or small for an Int32. Path 'ChildId', line 2, position 53.", + () => jsonTextReader.ReadAsInt32()); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.EndObject, jsonTextReader.TokenType); + + Assert.IsFalse(jsonTextReader.Read()); + } + + [Test] + public void ReadIntegerWithErrorInArray() + { + string json = @"[ + 333333333333333333333333333333333333333, + 3.3, + , + 0f +]"; + + JsonTextReader jsonTextReader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.StartArray, jsonTextReader.TokenType); + + ExceptionAssert.Throws( + "JSON integer 333333333333333333333333333333333333333 is too large or small for an Int32. Path '[0]', line 2, position 42.", + () => jsonTextReader.ReadAsInt32()); + + ExceptionAssert.Throws( + "Input string '3.3' is not a valid integer. Path '[1]', line 3, position 6.", + () => jsonTextReader.ReadAsInt32()); + + ExceptionAssert.Throws( + "Error reading integer. Unexpected token: Undefined. Path '[2]', line 4, position 3.", + () => jsonTextReader.ReadAsInt32()); + + ExceptionAssert.Throws( + "Input string '0f' is not a valid integer. Path '[3]', line 5, position 5.", + () => jsonTextReader.ReadAsInt32()); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.EndArray, jsonTextReader.TokenType); + + Assert.IsFalse(jsonTextReader.Read()); + } + + [Test] + public void ReadBytesWithError() + { + string json = @"{ + ChildId: '123' +}"; + + JsonTextReader jsonTextReader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.StartObject, jsonTextReader.TokenType); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.PropertyName, jsonTextReader.TokenType); + + ExceptionAssert.Throws( + "Invalid length for a Base-64 char array or string.", + () => jsonTextReader.ReadAsBytes()); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.EndObject, jsonTextReader.TokenType); + + Assert.IsFalse(jsonTextReader.Read()); + } + + [Test] + public void ReadBadMSDateAsString() + { + string json = @"{ + ChildId: '\/Date(9467082_PIE_340000-0631)\/' +}"; + + JsonTextReader jsonTextReader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.StartObject, jsonTextReader.TokenType); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.PropertyName, jsonTextReader.TokenType); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.String, jsonTextReader.TokenType); + Assert.AreEqual(@"/Date(9467082_PIE_340000-0631)/", jsonTextReader.Value); + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(JsonToken.EndObject, jsonTextReader.TokenType); + + Assert.IsFalse(jsonTextReader.Read()); + } + [Test] public void ReadInvalidNonBase10Number() { diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs index 7775f243..5560fd04 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs @@ -50,6 +50,22 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath [TestFixture] public class JPathExecuteTests : TestFixtureBase { + [Test] + public void SelectTokenAfterEmptyContainer() + { + string json = @"{ + 'cont': [], + 'test': 'no one will find me' +}"; + + JObject o = JObject.Parse(json); + + IList results = o.SelectTokens("$..test").ToList(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual("no one will find me", (string)results[0]); + } + [Test] public void EvaluatePropertyWithRequired() { diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 945ece0f..1c1562c1 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("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.3.17421")] +[assembly: AssemblyFileVersion("6.0.3.17428")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 5191a190..81320c06 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -91,6 +91,42 @@ namespace Newtonsoft.Json.Tests.Serialization [TestFixture] public class JsonSerializerTest : TestFixtureBase { + public class Link + { + /// + /// The unique identifier. + /// + public int Id; + + /// + /// The parent information identifier. + /// + public int ParentId; + + /// + /// The child information identifier. + /// + public int ChildId; + } + +#if !(NET20 || NET35 || PORTABLE40 || PORTABLE) + [Test] + public void ReadIntegerWithError() + { + string json = @"{ + ParentId: 1, + ChildId: 333333333333333333333333333333333333333 +}"; + + Link l = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + Error = (s, a) => a.ErrorContext.Handled = true + }); + + Assert.AreEqual(0, l.ChildId); + } +#endif + #if !NET20 [Test] public void PopulateResetSettings() diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index 57975f1a..1d028e0e 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -439,7 +439,6 @@ namespace Newtonsoft.Json if (t == JsonToken.Null) return null; - DateTimeOffset dt; if (t == JsonToken.String) { string s = (string)Value; @@ -450,6 +449,7 @@ namespace Newtonsoft.Json } object temp; + DateTimeOffset dt; if (DateTimeUtils.TryParseDateTime(s, DateParseHandling.DateTimeOffset, DateTimeZoneHandling, _dateFormatString, Culture, out temp)) { dt = (DateTimeOffset)temp; @@ -587,7 +587,6 @@ namespace Newtonsoft.Json if (t == JsonToken.Null) return null; - decimal d; if (t == JsonToken.String) { string s = (string)Value; @@ -597,6 +596,7 @@ namespace Newtonsoft.Json return null; } + decimal d; if (decimal.TryParse(s, NumberStyles.Number, Culture, out d)) { SetToken(JsonToken.Float, d, false); @@ -736,7 +736,6 @@ namespace Newtonsoft.Json if (TokenType == JsonToken.Null) return null; - DateTime dt; if (TokenType == JsonToken.String) { string s = (string)Value; @@ -746,6 +745,7 @@ namespace Newtonsoft.Json return null; } + DateTime dt; object temp; if (DateTimeUtils.TryParseDateTime(s, DateParseHandling.DateTime, DateTimeZoneHandling, _dateFormatString, Culture, out temp)) { @@ -878,17 +878,22 @@ namespace Newtonsoft.Json case JsonToken.String: case JsonToken.Raw: case JsonToken.Bytes: - if (Peek() != JsonContainerType.None) - _currentState = State.PostValue; - else - SetFinished(); - - if (updateIndex) - UpdateScopeWithFinishedValue(); + SetPostValueState(updateIndex); break; } } + internal void SetPostValueState(bool updateIndex) + { + if (Peek() != JsonContainerType.None) + _currentState = State.PostValue; + else + SetFinished(); + + if (updateIndex) + UpdateScopeWithFinishedValue(); + } + private void UpdateScopeWithFinishedValue() { if (_currentPosition.HasIndex) diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 9c6df61d..b91d74d7 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -115,6 +115,7 @@ namespace Newtonsoft.Json ShiftBufferIfNeeded(); ReadStringIntoBuffer(quote); + SetPostValueState(true); if (_readType == ReadType.ReadAsBytes) { @@ -128,13 +129,13 @@ namespace Newtonsoft.Json data = Convert.FromBase64CharArray(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length); } - SetToken(JsonToken.Bytes, data); + SetToken(JsonToken.Bytes, data, false); } else if (_readType == ReadType.ReadAsString) { string text = _stringReference.ToString(); - SetToken(JsonToken.String, text); + SetToken(JsonToken.String, text, false); _quoteChar = quote; } else @@ -156,12 +157,12 @@ namespace Newtonsoft.Json object dt; if (DateTimeUtils.TryParseDateTime(text, dateParseHandling, DateTimeZoneHandling, DateFormatString, Culture, out dt)) { - SetToken(JsonToken.Date, dt); + SetToken(JsonToken.Date, dt, false); return; } } - SetToken(JsonToken.String, text); + SetToken(JsonToken.String, text, false); _quoteChar = quote; } } @@ -381,8 +382,6 @@ namespace Newtonsoft.Json case State.Constructor: case State.ConstructorStart: return ParseValue(); - case State.Complete: - break; case State.Object: case State.ObjectStart: return ParseObject(); @@ -405,16 +404,10 @@ namespace Newtonsoft.Json ParseComment(); return true; } - else - { - throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); - } + + throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); } return false; - case State.Closed: - break; - case State.Error: - break; default: throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); } @@ -1004,15 +997,13 @@ namespace Newtonsoft.Json _charPos++; break; } - else if (char.IsNumber(currentChar) || currentChar == '-' || currentChar == '.') + if (char.IsNumber(currentChar) || currentChar == '-' || currentChar == '.') { ParseNumber(); return true; } - else - { - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); - } + + throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); } } } @@ -1165,6 +1156,9 @@ namespace Newtonsoft.Json ReadNumberIntoBuffer(); + // set state to PostValue now so that if there is an error parsing the number then the reader can continue + SetPostValueState(true); + _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition); object numberValue; @@ -1328,7 +1322,8 @@ namespace Newtonsoft.Json ClearRecentString(); - SetToken(numberType, numberValue); + // index has already been updated + SetToken(numberType, numberValue, false); } private void ParseComment() diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index abcd34c5..aa1f6de7 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("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.3.17421")] +[assembly: AssemblyFileVersion("6.0.3.17428")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs b/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs index 3cecc9be..5a0f3c09 100644 --- a/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs @@ -332,7 +332,12 @@ namespace Newtonsoft.Json.Utilities value = value.Substring(0, index); } - long javaScriptTicks = long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + long javaScriptTicks; + if (!long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out javaScriptTicks)) + { + dt = null; + return false; + } DateTime utcDateTime = ConvertJavaScriptTicksToDateTime(javaScriptTicks); @@ -342,27 +347,24 @@ namespace Newtonsoft.Json.Utilities dt = new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset); return true; } - else #endif + + DateTime dateTime; + switch (kind) { - DateTime dateTime; - - switch (kind) - { - case DateTimeKind.Unspecified: - dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified); - break; - case DateTimeKind.Local: - dateTime = utcDateTime.ToLocalTime(); - break; - default: - dateTime = utcDateTime; - break; - } - - dt = EnsureDateTime(dateTime, dateTimeZoneHandling); - return true; + case DateTimeKind.Unspecified: + dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified); + break; + case DateTimeKind.Local: + dateTime = utcDateTime.ToLocalTime(); + break; + default: + dateTime = utcDateTime; + break; } + + dt = EnsureDateTime(dateTime, dateTimeZoneHandling); + return true; } private static bool TryParseDateExact(string text, DateParseHandling dateParseHandling, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out object dt) diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index c76c84eb..ca32ebb5 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -367,21 +367,19 @@ namespace Newtonsoft.Json.Utilities { return type.GetElementType(); } - else if (ImplementsGenericDefinition(type, typeof(IEnumerable<>), out genericListType)) + if (ImplementsGenericDefinition(type, typeof(IEnumerable<>), out genericListType)) { if (genericListType.IsGenericTypeDefinition()) throw new Exception("Type {0} is not a collection.".FormatWith(CultureInfo.InvariantCulture, type)); return genericListType.GetGenericArguments()[0]; } - else if (typeof(IEnumerable).IsAssignableFrom(type)) + if (typeof(IEnumerable).IsAssignableFrom(type)) { return null; } - else - { - throw new Exception("Type {0} is not a collection.".FormatWith(CultureInfo.InvariantCulture, type)); - } + + throw new Exception("Type {0} is not a collection.".FormatWith(CultureInfo.InvariantCulture, type)); } public static void GetDictionaryKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType) @@ -400,16 +398,14 @@ namespace Newtonsoft.Json.Utilities valueType = dictionaryGenericArguments[1]; return; } - else if (typeof(IDictionary).IsAssignableFrom(dictionaryType)) + if (typeof(IDictionary).IsAssignableFrom(dictionaryType)) { keyType = null; valueType = null; return; } - else - { - throw new Exception("Type {0} is not a dictionary.".FormatWith(CultureInfo.InvariantCulture, dictionaryType)); - } + + throw new Exception("Type {0} is not a dictionary.".FormatWith(CultureInfo.InvariantCulture, dictionaryType)); } /// @@ -432,7 +428,7 @@ namespace Newtonsoft.Json.Utilities case MemberTypes.Method: return ((MethodInfo)member).ReturnType; default: - throw new ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or EventInfo", "member"); + throw new ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo, EventInfo or MethodInfo", "member"); } }