diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index e0a71ce7..efde893d 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -305,7 +305,7 @@ third line", jsonTextReader.Value); ExceptionAssert.Throws(() => jsonTextReader.ReadAsInt32(), "Input string '3.3' is not a valid integer. Path '[1]', line 3, position 5."); - ExceptionAssert.Throws(() => jsonTextReader.ReadAsInt32(), "Error reading integer. Unexpected token: Undefined. Path '[2]', line 4, position 2."); + ExceptionAssert.Throws(() => jsonTextReader.ReadAsInt32(), "Unexpected character encountered while parsing value: ,. Path '[2]', line 4, position 2."); ExceptionAssert.Throws(() => jsonTextReader.ReadAsInt32(), "Input string '0f' is not a valid integer. Path '[3]', line 5, position 4."); @@ -2087,7 +2087,16 @@ third line", jsonTextReader.Value); { JsonTextReader reader = new JsonTextReader(new StringReader(@"new Date()")); - ExceptionAssert.Throws(() => { reader.ReadAsDecimal(); }, "Error reading decimal. Unexpected token: StartConstructor. Path '', line 1, position 9."); + ExceptionAssert.Throws(() => { reader.ReadAsDecimal(); }, "Unexpected character encountered while parsing value: e. Path '', line 1, position 2."); + } + + [Test] + public void ReadAsDecimalBadContent_SecondLine() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@" +new Date()")); + + ExceptionAssert.Throws(() => { reader.ReadAsDecimal(); }, "Unexpected character encountered while parsing value: e. Path '', line 2, position 2."); } [Test] @@ -2104,7 +2113,7 @@ third line", jsonTextReader.Value); { JsonTextReader reader = new JsonTextReader(new StringReader(@"new Date()")); - ExceptionAssert.Throws(() => { reader.ReadAsDateTimeOffset(); }, "Error reading date. Unexpected token: StartConstructor. Path '', line 1, position 9."); + ExceptionAssert.Throws(() => { reader.ReadAsDateTimeOffset(); }, "Unexpected character encountered while parsing value: e. Path '', line 1, position 2."); } #endif @@ -2892,7 +2901,7 @@ null//comment Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Comment, reader.TokenType); Assert.AreEqual("comment*//*hi*/", reader.Value); - Assert.AreEqual(2, reader.LineNumber); + Assert.AreEqual(1, reader.LineNumber); Assert.IsTrue(reader.Read()); Assert.AreEqual(2, reader.LineNumber); @@ -2900,7 +2909,7 @@ null//comment Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Comment, reader.TokenType); - Assert.AreEqual(3, reader.LineNumber); + Assert.AreEqual(2, reader.LineNumber); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); @@ -2909,7 +2918,7 @@ null//comment Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Comment, reader.TokenType); - Assert.AreEqual(4, reader.LineNumber); + Assert.AreEqual(3, reader.LineNumber); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Boolean, reader.TokenType); @@ -2919,12 +2928,12 @@ null//comment Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Comment, reader.TokenType); Assert.AreEqual("comment after true", reader.Value); - Assert.AreEqual(5, reader.LineNumber); + Assert.AreEqual(4, reader.LineNumber); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Comment, reader.TokenType); Assert.AreEqual("comment after comma", reader.Value); - Assert.AreEqual(6, reader.LineNumber); + Assert.AreEqual(5, reader.LineNumber); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); @@ -2933,7 +2942,7 @@ null//comment Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Comment, reader.TokenType); - Assert.AreEqual(7, reader.LineNumber); + Assert.AreEqual(6, reader.LineNumber); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.StartConstructor, reader.TokenType); @@ -2949,7 +2958,7 @@ null//comment Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Comment, reader.TokenType); - Assert.AreEqual(11, reader.LineNumber); + Assert.AreEqual(10, reader.LineNumber); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.EndConstructor, reader.TokenType); @@ -3001,6 +3010,44 @@ null//comment Assert.IsFalse(reader.Read()); } + [Test] + public void ReadNullIntLineNumberAndPosition() + { + string json = @"[ + 1, + 2, + 3, + null +]"; + + JsonTextReader reader = new JsonTextReader(new StreamReader(new SlowStream(json, new UTF8Encoding(false), 1))); + + reader.Read(); + Assert.AreEqual(1, reader.LineNumber); + + reader.ReadAsInt32(); + Assert.AreEqual(2, reader.LineNumber); + Assert.AreEqual("[0]", reader.Path); + + reader.ReadAsInt32(); + Assert.AreEqual(3, reader.LineNumber); + Assert.AreEqual("[1]", reader.Path); + + reader.ReadAsInt32(); + Assert.AreEqual(4, reader.LineNumber); + Assert.AreEqual("[2]", reader.Path); + + reader.ReadAsInt32(); + Assert.AreEqual(5, reader.LineNumber); + Assert.AreEqual("[3]", reader.Path); + + reader.Read(); + Assert.AreEqual(6, reader.LineNumber); + Assert.AreEqual(string.Empty, reader.Path); + + Assert.IsFalse(reader.Read()); + } + [Test] public void JustSinglelineComment() { diff --git a/Src/Newtonsoft.Json.Tests/Linq/LinqToJsonTest.cs b/Src/Newtonsoft.Json.Tests/Linq/LinqToJsonTest.cs index 769439f4..a9b7f569 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/LinqToJsonTest.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/LinqToJsonTest.cs @@ -221,8 +221,8 @@ namespace Newtonsoft.Json.Tests.Linq IJsonLineInfo lineInfo = v; Assert.AreEqual(true, lineInfo.HasLineInfo()); - Assert.AreEqual(3, lineInfo.LineNumber); - Assert.AreEqual(0, lineInfo.LinePosition); + Assert.AreEqual(2, lineInfo.LineNumber); + Assert.AreEqual(5, lineInfo.LinePosition); } [Test] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 35403919..427e2565 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -377,6 +377,10 @@ namespace Newtonsoft.Json.Tests.Serialization [Test] public void IncompleteContainers() { + ExceptionAssert.Throws( + () => JsonConvert.DeserializeObject>("[1,"), + "Unexpected end when deserializing array. Path '[0]', line 1, position 3."); + ExceptionAssert.Throws( () => JsonConvert.DeserializeObject>("[1,"), "Unexpected end when deserializing array. Path '[0]', line 1, position 3."); @@ -6960,7 +6964,7 @@ Path '', line 1, position 1."); }"; JsonConvert.DeserializeObject(json); - }, "Error reading integer. Unexpected token: Boolean. Path 'PreProperty', line 2, position 21."); + }, "Unexpected character encountered while parsing value: t. Path 'PreProperty', line 2, position 17."); } [Test] diff --git a/Src/Newtonsoft.Json/Bson/BsonReader.cs b/Src/Newtonsoft.Json/Bson/BsonReader.cs index f6deace3..276adf18 100644 --- a/Src/Newtonsoft.Json/Bson/BsonReader.cs +++ b/Src/Newtonsoft.Json/Bson/BsonReader.cs @@ -174,66 +174,6 @@ namespace Newtonsoft.Json.Bson return elementName; } - /// - /// Reads the next JSON token from the stream as a []. - /// - /// - /// A [] or a null reference if the next JSON token is null. This method will return null at the end of an array. - /// - public override byte[] ReadAsBytes() - { - return ReadAsBytesInternal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override decimal? ReadAsDecimal() - { - return ReadAsDecimalInternal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override int? ReadAsInt32() - { - return ReadAsInt32Internal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override string ReadAsString() - { - return ReadAsStringInternal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override DateTime? ReadAsDateTime() - { - return ReadAsDateTimeInternal(); - } - -#if !NET20 - /// - /// Reads the next JSON token from the stream as a . - /// - /// - /// A . This method will return null at the end of an array. - /// - public override DateTimeOffset? ReadAsDateTimeOffset() - { - return ReadAsDateTimeOffsetInternal(); - } -#endif - /// /// Reads the next JSON token from the stream. /// @@ -241,13 +181,6 @@ namespace Newtonsoft.Json.Bson /// true if the next token was read successfully; false if there are no more tokens to read. /// public override bool Read() - { - _readType = Json.ReadType.Read; - - return ReadInternal(); - } - - internal override bool ReadInternal() { try { diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index e15a6496..b8df2dce 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -119,7 +119,6 @@ namespace Newtonsoft.Json private object _value; internal char _quoteChar; internal State _currentState; - internal ReadType _readType; private JsonPosition _currentPosition; private CultureInfo _culture; private DateTimeZoneHandling _dateTimeZoneHandling; @@ -413,222 +412,9 @@ 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 abstract int? ReadAsInt32(); - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public abstract string ReadAsString(); - - /// - /// Reads the next JSON token from the stream as a []. - /// - /// A [] or a null reference if the next JSON token is null. This method will return null at the end of an array. - public abstract byte[] ReadAsBytes(); - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public abstract decimal? ReadAsDecimal(); - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public abstract DateTime? ReadAsDateTime(); - -#if !NET20 - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public abstract DateTimeOffset? ReadAsDateTimeOffset(); -#endif - - internal virtual bool ReadInternal() + public virtual int? ReadAsInt32() { - throw new NotImplementedException(); - } - -#if !NET20 - internal DateTimeOffset? ReadAsDateTimeOffsetInternal() - { - _readType = ReadType.ReadAsDateTimeOffset; - - JsonToken t = GetContentTokenInternal(); - - switch (t) - { - case JsonToken.None: - case JsonToken.Null: - case JsonToken.EndArray: - return null; - case JsonToken.Date: - if (Value is DateTime) - { - SetToken(JsonToken.Date, new DateTimeOffset((DateTime)Value), false); - } - - return (DateTimeOffset)Value; - case JsonToken.String: - string s = (string)Value; - if (string.IsNullOrEmpty(s)) - { - SetToken(JsonToken.Null); - return null; - } - - DateTimeOffset dt; - if (DateTimeUtils.TryParseDateTimeOffset(s, _dateFormatString, Culture, out dt)) - { - SetToken(JsonToken.Date, dt, false); - return dt; - } - - if (DateTimeOffset.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt)) - { - SetToken(JsonToken.Date, dt, false); - return dt; - } - - throw JsonReaderException.Create(this, "Could not convert string to DateTimeOffset: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - default: - throw JsonReaderException.Create(this, "Error reading date. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); - } - } -#endif - - internal byte[] ReadAsBytesInternal() - { - _readType = ReadType.ReadAsBytes; - - JsonToken t = GetContentTokenInternal(); - - if (t == JsonToken.None) - { - return null; - } - - if (IsWrappedInTypeObject()) - { - byte[] data = ReadAsBytes(); - ReadInternal(); - SetToken(JsonToken.Bytes, data, false); - return data; - } - - switch (t) - { - case JsonToken.String: - { - // attempt to convert possible base 64 or GUID string to bytes - // GUID has to have format 00000000-0000-0000-0000-000000000000 - string s = (string)Value; - - byte[] data; - - Guid g; - if (s.Length == 0) - { - data = new byte[0]; - } - else if (ConvertUtils.TryConvertGuid(s, out g)) - { - data = g.ToByteArray(); - } - else - { - data = Convert.FromBase64String(s); - } - - SetToken(JsonToken.Bytes, data, false); - return data; - } - case JsonToken.Null: - case JsonToken.EndArray: - return null; - case JsonToken.Bytes: - if (ValueType == typeof(Guid)) - { - byte[] data = ((Guid)Value).ToByteArray(); - SetToken(JsonToken.Bytes, data, false); - return data; - } - - return (byte[])Value; - case JsonToken.StartArray: - List buffer = new List(); - - while (true) - { - switch (t = GetContentTokenInternal()) - { - case JsonToken.None: - throw JsonReaderException.Create(this, "Unexpected end when reading bytes."); - case JsonToken.Integer: - buffer.Add(Convert.ToByte(Value, CultureInfo.InvariantCulture)); - break; - case JsonToken.EndArray: - byte[] d = buffer.ToArray(); - SetToken(JsonToken.Bytes, d, false); - return d; - default: - throw JsonReaderException.Create(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); - } - } - } - throw JsonReaderException.Create(this, "Error reading bytes. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); - } - - internal decimal? ReadAsDecimalInternal() - { - _readType = ReadType.ReadAsDecimal; - - JsonToken t = GetContentTokenInternal(); - - switch (t) - { - case JsonToken.None: - case JsonToken.Null: - case JsonToken.EndArray: - return null; - case JsonToken.Integer: - case JsonToken.Float: - if (!(Value is decimal)) - { - SetToken(JsonToken.Float, Convert.ToDecimal(Value, CultureInfo.InvariantCulture), false); - } - - return (decimal)Value; - case JsonToken.String: - string s = (string)Value; - if (string.IsNullOrEmpty(s)) - { - SetToken(JsonToken.Null); - return null; - } - - decimal d; - if (decimal.TryParse(s, NumberStyles.Number, Culture, out d)) - { - SetToken(JsonToken.Float, d, false); - return d; - } - else - { - throw JsonReaderException.Create(this, "Could not convert string to decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } - } - throw JsonReaderException.Create(this, "Error reading decimal. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); - } - - internal int? ReadAsInt32Internal() - { - _readType = ReadType.ReadAsInt32; - - switch (GetContentTokenInternal()) + switch (GetContentToken()) { case JsonToken.None: case JsonToken.Null: @@ -644,31 +430,40 @@ namespace Newtonsoft.Json return (int)Value; case JsonToken.String: string s = (string)Value; - if (string.IsNullOrEmpty(s)) - { - SetToken(JsonToken.Null); - return null; - } - - int i; - if (int.TryParse(s, NumberStyles.Integer, Culture, out i)) - { - SetToken(JsonToken.Integer, i, false); - return i; - } - else - { - throw JsonReaderException.Create(this, "Could not convert string to integer: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } + return ReadInt32String(s); } + throw JsonReaderException.Create(this, "Error reading integer. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } - internal string ReadAsStringInternal() + internal int? ReadInt32String(string s) { - _readType = ReadType.ReadAsString; + if (string.IsNullOrEmpty(s)) + { + SetToken(JsonToken.Null); + return null; + } - JsonToken t = GetContentTokenInternal(); + int i; + if (int.TryParse(s, NumberStyles.Integer, Culture, out i)) + { + SetToken(JsonToken.Integer, i, false); + return i; + } + else + { + SetToken(JsonToken.String, s, false); + throw JsonReaderException.Create(this, "Could not convert string to integer: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); + } + } + + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public virtual string ReadAsString() + { + JsonToken t = GetContentToken(); switch (t) { @@ -702,11 +497,148 @@ namespace Newtonsoft.Json throw JsonReaderException.Create(this, "Error reading string. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } - internal DateTime? ReadAsDateTimeInternal() + /// + /// Reads the next JSON token from the stream as a []. + /// + /// A [] or a null reference if the next JSON token is null. This method will return null at the end of an array. + public virtual byte[] ReadAsBytes() { - _readType = ReadType.ReadAsDateTime; + JsonToken t = GetContentToken(); - switch (GetContentTokenInternal()) + if (t == JsonToken.None) + { + return null; + } + + if (IsWrappedInTypeObject()) + { + byte[] data = ReadAsBytes(); + Read(); + SetToken(JsonToken.Bytes, data, false); + return data; + } + + switch (t) + { + case JsonToken.String: + { + // attempt to convert possible base 64 or GUID string to bytes + // GUID has to have format 00000000-0000-0000-0000-000000000000 + string s = (string)Value; + + byte[] data; + + Guid g; + if (s.Length == 0) + { + data = new byte[0]; + } + else if (ConvertUtils.TryConvertGuid(s, out g)) + { + data = g.ToByteArray(); + } + else + { + data = Convert.FromBase64String(s); + } + + SetToken(JsonToken.Bytes, data, false); + return data; + } + case JsonToken.Null: + case JsonToken.EndArray: + return null; + case JsonToken.Bytes: + if (ValueType == typeof(Guid)) + { + byte[] data = ((Guid)Value).ToByteArray(); + SetToken(JsonToken.Bytes, data, false); + return data; + } + + return (byte[])Value; + case JsonToken.StartArray: + List buffer = new List(); + + while (true) + { + switch (t = GetContentToken()) + { + case JsonToken.None: + throw JsonReaderException.Create(this, "Unexpected end when reading bytes."); + case JsonToken.Integer: + buffer.Add(Convert.ToByte(Value, CultureInfo.InvariantCulture)); + break; + case JsonToken.EndArray: + byte[] d = buffer.ToArray(); + SetToken(JsonToken.Bytes, d, false); + return d; + default: + throw JsonReaderException.Create(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); + } + } + } + + throw JsonReaderException.Create(this, "Error reading bytes. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); + } + + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public virtual decimal? ReadAsDecimal() + { + 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 decimal)) + { + SetToken(JsonToken.Float, Convert.ToDecimal(Value, CultureInfo.InvariantCulture), false); + } + + return (decimal)Value; + case JsonToken.String: + return ReadDecimalString((string)Value); + } + + throw JsonReaderException.Create(this, "Error reading decimal. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); + } + + internal decimal? ReadDecimalString(string s) + { + if (string.IsNullOrEmpty(s)) + { + SetToken(JsonToken.Null); + return null; + } + + decimal d; + if (decimal.TryParse(s, NumberStyles.Number, 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 decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); + } + } + + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public virtual DateTime? ReadAsDateTime() + { + switch (GetContentToken()) { case JsonToken.None: case JsonToken.Null: @@ -716,51 +648,109 @@ namespace Newtonsoft.Json return (DateTime)Value; case JsonToken.String: string s = (string)Value; - if (string.IsNullOrEmpty(s)) - { - SetToken(JsonToken.Null); - return null; - } - - DateTime dt; - if (DateTimeUtils.TryParseDateTime(s, DateTimeZoneHandling, _dateFormatString, Culture, out dt)) - { - dt = DateTimeUtils.EnsureDateTime(dt, DateTimeZoneHandling); - SetToken(JsonToken.Date, dt, false); - return dt; - } - - if (DateTime.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt)) - { - dt = DateTimeUtils.EnsureDateTime(dt, DateTimeZoneHandling); - SetToken(JsonToken.Date, dt, false); - return dt; - } - - throw JsonReaderException.Create(this, "Could not convert string to DateTime: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - + return ReadDateTimeString(s); } throw JsonReaderException.Create(this, "Error reading date. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } + internal DateTime? ReadDateTimeString(string s) + { + if (string.IsNullOrEmpty(s)) + { + SetToken(JsonToken.Null); + return null; + } + + DateTime dt; + if (DateTimeUtils.TryParseDateTime(s, DateTimeZoneHandling, _dateFormatString, Culture, out dt)) + { + dt = DateTimeUtils.EnsureDateTime(dt, DateTimeZoneHandling); + SetToken(JsonToken.Date, dt, false); + return dt; + } + + if (DateTime.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt)) + { + dt = DateTimeUtils.EnsureDateTime(dt, DateTimeZoneHandling); + SetToken(JsonToken.Date, dt, false); + return dt; + } + + throw JsonReaderException.Create(this, "Could not convert string to DateTime: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); + } + +#if !NET20 + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public virtual DateTimeOffset? ReadAsDateTimeOffset() + { + JsonToken t = GetContentToken(); + + switch (t) + { + case JsonToken.None: + case JsonToken.Null: + case JsonToken.EndArray: + return null; + case JsonToken.Date: + if (Value is DateTime) + { + SetToken(JsonToken.Date, new DateTimeOffset((DateTime)Value), false); + } + + return (DateTimeOffset)Value; + case JsonToken.String: + string s = (string)Value; + return ReadDateTimeOffsetString(s); + default: + throw JsonReaderException.Create(this, "Error reading date. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); + } + } + + internal DateTimeOffset? ReadDateTimeOffsetString(string s) + { + if (string.IsNullOrEmpty(s)) + { + SetToken(JsonToken.Null); + return null; + } + + DateTimeOffset dt; + if (DateTimeUtils.TryParseDateTimeOffset(s, _dateFormatString, Culture, out dt)) + { + SetToken(JsonToken.Date, dt, false); + return dt; + } + + if (DateTimeOffset.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt)) + { + SetToken(JsonToken.Date, dt, false); + return dt; + } + + SetToken(JsonToken.String, s, false); + throw JsonReaderException.Create(this, "Could not convert string to DateTimeOffset: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); + } +#endif + private bool IsWrappedInTypeObject() { - _readType = ReadType.Read; - if (TokenType == JsonToken.StartObject) { - if (!ReadInternal()) + if (!Read()) { throw JsonReaderException.Create(this, "Unexpected end when reading bytes."); } if (Value.ToString() == JsonTypeReflector.TypePropertyName) { - ReadInternal(); + Read(); if (Value != null && Value.ToString().StartsWith("System.Byte[]", StringComparison.Ordinal)) { - ReadInternal(); + Read(); if (Value.ToString() == JsonTypeReflector.ValuePropertyName) { return true; @@ -1017,12 +1007,12 @@ namespace Newtonsoft.Json return true; } - private JsonToken GetContentTokenInternal() + private JsonToken GetContentToken() { JsonToken t; do { - if (!ReadInternal()) + if (!Read()) { SetToken(JsonToken.None); return JsonToken.None; diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index ebf5e18c..8fe33aee 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -120,7 +120,7 @@ namespace Newtonsoft.Json _lineStartPos = pos; } - private void ParseString(char quote) + private void ParseString(char quote, ReadType readType) { _charPos++; @@ -128,7 +128,7 @@ namespace Newtonsoft.Json ReadStringIntoBuffer(quote); SetPostValueState(true); - if (_readType == ReadType.ReadAsBytes) + if (readType == ReadType.ReadAsBytes) { Guid g; byte[] data; @@ -147,24 +147,29 @@ namespace Newtonsoft.Json SetToken(JsonToken.Bytes, data, false); } - else if (_readType == ReadType.ReadAsString) + else if (readType == ReadType.ReadAsString) { string text = _stringReference.ToString(); SetToken(JsonToken.String, text, false); _quoteChar = quote; } + else if (readType == ReadType.ReadAsInt32 || + readType == ReadType.ReadAsDecimal) + { + // caller will convert result + } else { if (_dateParseHandling != DateParseHandling.None) { DateParseHandling dateParseHandling; - if (_readType == ReadType.ReadAsDateTime) + if (readType == ReadType.ReadAsDateTime) { dateParseHandling = DateParseHandling.DateTime; } #if !NET20 - else if (_readType == ReadType.ReadAsDateTimeOffset) + else if (readType == ReadType.ReadAsDateTimeOffset) { dateParseHandling = DateParseHandling.DateTimeOffset; } @@ -205,7 +210,7 @@ namespace Newtonsoft.Json { const int charByteCount = 2; - Buffer.BlockCopy(src, srcOffset * charByteCount, dst, dstOffset * charByteCount, count * charByteCount); + Buffer.BlockCopy(src, srcOffset*charByteCount, dst, dstOffset*charByteCount, count*charByteCount); } private void ShiftBufferIfNeeded() @@ -213,7 +218,7 @@ namespace Newtonsoft.Json // once in the last 10% of the buffer shift the remaining content to the start to avoid // unnessesarly increasing the buffer size when reading numbers/strings int length = _chars.Length; - if (length - _charPos <= length * 0.1) + if (length - _charPos <= length*0.1) { int count = _charsUsed - _charPos; if (count > 0) @@ -246,7 +251,7 @@ namespace Newtonsoft.Json if (append) { // copy to new array either double the size of the current or big enough to fit required content - int newArrayLength = Math.Max(_chars.Length * 2, _charsUsed + charsRequired + 1); + int newArrayLength = Math.Max(_chars.Length*2, _charsUsed + charsRequired + 1); // increase the size of the buffer char[] dst = BufferUtils.RentBuffer(_arrayPool, newArrayLength); @@ -354,84 +359,9 @@ namespace Newtonsoft.Json /// /// true if the next token was read successfully; false if there are no more tokens to read. /// - [DebuggerStepThrough] public override bool Read() { - _readType = ReadType.Read; - if (!ReadInternal()) - { - SetToken(JsonToken.None); - return false; - } - - return true; - } - - /// - /// Reads the next JSON token from the stream as a []. - /// - /// - /// A [] or a null reference if the next JSON token is null. This method will return null at the end of an array. - /// - public override byte[] ReadAsBytes() - { - return ReadAsBytesInternal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override decimal? ReadAsDecimal() - { - return ReadAsDecimalInternal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override int? ReadAsInt32() - { - return ReadAsInt32Internal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override string ReadAsString() - { - return ReadAsStringInternal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override DateTime? ReadAsDateTime() - { - return ReadAsDateTimeInternal(); - } - -#if !NET20 - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override DateTimeOffset? ReadAsDateTimeOffset() - { - return ReadAsDateTimeOffsetInternal(); - } -#endif - - internal override bool ReadInternal() - { - if (_chars == null) - { - _chars = BufferUtils.RentBuffer(_arrayPool, 1024); - _chars[0] = '\0'; - } + EnsureBuffer(); while (true) { @@ -461,16 +391,18 @@ namespace Newtonsoft.Json EatWhitespace(false); if (_isEndOfFile) { + SetToken(JsonToken.None); return false; } if (_chars[_charPos] == '/') { - ParseComment(); + ParseComment(true); return true; } throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); } + SetToken(JsonToken.None); return false; default: throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); @@ -478,6 +410,353 @@ 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 override int? ReadAsInt32() + { + return (int?) ReadNumberValue(ReadType.ReadAsInt32); + } + + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override DateTime? ReadAsDateTime() + { + return (DateTime?) ReadStringValue(ReadType.ReadAsDateTime); + } + + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override string ReadAsString() + { + return (string) ReadStringValue(ReadType.ReadAsString); + } + + private object ReadStringValue(ReadType readType) + { + EnsureBuffer(); + + switch (_currentState) + { + case State.Start: + case State.Property: + case State.Array: + case State.ArrayStart: + case State.Constructor: + case State.ConstructorStart: + case State.PostValue: + while (true) + { + char currentChar = _chars[_charPos]; + + switch (currentChar) + { + case '\0': + if (ReadNullChar()) + { + SetToken(JsonToken.None, null, false); + return null; + } + break; + case '"': + case '\'': + ParseString(currentChar, readType); + switch (readType) + { + case ReadType.ReadAsBytes: + return Value; + case ReadType.ReadAsString: + return Value; + case ReadType.ReadAsDateTime: + if (Value is DateTime) + { + return (DateTime) Value; + } + return ReadDateTimeString((string) Value); +#if !NET20 + case ReadType.ReadAsDateTimeOffset: + if (Value is DateTimeOffset) + { + return (DateTimeOffset) Value; + } + return ReadDateTimeOffsetString((string) Value); +#endif + default: + throw new ArgumentOutOfRangeException(nameof(readType)); + } + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (readType != ReadType.ReadAsString) + { + throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + } + ParseNumber(ReadType.Read); + string s = ((IFormattable) Value).ToString(null, CultureInfo.InvariantCulture); + SetToken(JsonToken.String, s); + return s; + case 'n': + HandleNull(); + return null; + case '/': + ParseComment(false); + break; + case ',': + if (_currentState != State.PostValue) + { + SetToken(JsonToken.Undefined); + throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + } + + _charPos++; + SetStateBasedOnCurrent(); + break; + case '}': + _charPos++; + SetToken(JsonToken.EndObject); + return null; + case ']': + _charPos++; + SetToken(JsonToken.EndArray); + return null; + case StringUtils.CarriageReturn: + ProcessCarriageReturn(false); + break; + case StringUtils.LineFeed: + ProcessLineFeed(); + break; + case ' ': + case StringUtils.Tab: + // eat + _charPos++; + break; + default: + if (char.IsWhiteSpace(currentChar)) + { + // eat + _charPos++; + break; + } + + throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + } + } + case State.Finished: + ReadFinished(); + SetToken(JsonToken.None); + return null; + default: + throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); + } + } + +#if !NET20 + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override DateTimeOffset? ReadAsDateTimeOffset() + { + return (DateTimeOffset?) ReadStringValue(ReadType.ReadAsDateTimeOffset); + } +#endif + + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override decimal? ReadAsDecimal() + { + return (decimal?) ReadNumberValue(ReadType.ReadAsDecimal); + } + + private object ReadNumberValue(ReadType readType) + { + EnsureBuffer(); + + switch (_currentState) + { + case State.Start: + case State.Property: + case State.Array: + case State.ArrayStart: + case State.Constructor: + case State.ConstructorStart: + case State.PostValue: + while (true) + { + char currentChar = _chars[_charPos]; + + switch (currentChar) + { + case '\0': + if (ReadNullChar()) + { + SetToken(JsonToken.None, null, false); + return null; + } + break; + case '"': + case '\'': + ParseString(currentChar, readType); + switch (readType) + { + case ReadType.ReadAsInt32: + return ReadInt32String(_stringReference.ToString()); + case ReadType.ReadAsDecimal: + return ReadDecimalString(_stringReference.ToString()); + default: + throw new ArgumentOutOfRangeException(nameof(readType)); + } + case 'n': + HandleNull(); + return null; + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ParseNumber(readType); + return Value; + case '/': + ParseComment(false); + break; + case ',': + if (_currentState != State.PostValue) + { + SetToken(JsonToken.Undefined); + throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + } + + _charPos++; + SetStateBasedOnCurrent(); + break; + case '}': + _charPos++; + SetToken(JsonToken.EndObject); + return null; + case ']': + _charPos++; + SetToken(JsonToken.EndArray); + return null; + case StringUtils.CarriageReturn: + ProcessCarriageReturn(false); + break; + case StringUtils.LineFeed: + ProcessLineFeed(); + break; + case ' ': + case StringUtils.Tab: + // eat + _charPos++; + break; + default: + if (char.IsWhiteSpace(currentChar)) + { + // eat + _charPos++; + break; + } + + throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + } + } + case State.Finished: + ReadFinished(); + SetToken(JsonToken.None); + return null; + default: + throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); + } + } + + private void HandleNull() + { + if (EnsureChars(1, true)) + { + char next = _chars[_charPos + 1]; + + if (next == 'u') + { + ParseNull(); + return; + } + + _charPos += 2; + throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos - 1])); + } + + throw JsonReaderException.Create(this, "Unexpected end when reading JSON."); + } + + private void ReadFinished() + { + if (EnsureChars(0, false)) + { + EatWhitespace(false); + if (_isEndOfFile) + { + return; + } + if (_chars[_charPos] == '/') + { + ParseComment(false); + } + else + { + throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); + } + } + } + + private bool ReadNullChar() + { + if (_charsUsed == _charPos) + { + if (ReadData(false) == 0) + { + _isEndOfFile = true; + return true; + } + } + else + { + _charPos++; + } + + return false; + } + + private void EnsureBuffer() + { + if (_chars == null) + { + _chars = BufferUtils.RentBuffer(_arrayPool, 1024); + _chars[0] = '\0'; + } + } + private void ReadStringIntoBuffer(char quote) { int charPos = _charPos; @@ -733,12 +1012,7 @@ namespace Newtonsoft.Json _charPos = charPos; char currentChar = _chars[_charPos]; - if (char.IsWhiteSpace(currentChar) - || currentChar == ',' - || currentChar == '}' - || currentChar == ']' - || currentChar == ')' - || currentChar == '/') + if (char.IsWhiteSpace(currentChar) || currentChar == ',' || currentChar == '}' || currentChar == ']' || currentChar == ')' || currentChar == '/') { return; } @@ -789,7 +1063,7 @@ namespace Newtonsoft.Json SetToken(JsonToken.EndConstructor); return true; case '/': - ParseComment(); + ParseComment(true); return true; case ',': _charPos++; @@ -849,7 +1123,7 @@ namespace Newtonsoft.Json _charPos++; return true; case '/': - ParseComment(); + ParseComment(true); return true; case StringUtils.CarriageReturn: ProcessCarriageReturn(false); @@ -1002,7 +1276,7 @@ namespace Newtonsoft.Json break; case '"': case '\'': - ParseString(currentChar); + ParseString(currentChar, ReadType.Read); return true; case 't': ParseTrue(); @@ -1046,11 +1320,11 @@ namespace Newtonsoft.Json } else { - ParseNumber(); + ParseNumber(ReadType.Read); } return true; case '/': - ParseComment(); + ParseComment(true); return true; case 'u': ParseUndefined(); @@ -1096,7 +1370,7 @@ namespace Newtonsoft.Json } if (char.IsNumber(currentChar) || currentChar == '-' || currentChar == '.') { - ParseNumber(); + ParseNumber(ReadType.Read); return true; } @@ -1252,7 +1526,7 @@ namespace Newtonsoft.Json } } - private void ParseNumber() + private void ParseNumber(ReadType readType) { ShiftBufferIfNeeded(); @@ -1270,12 +1544,9 @@ namespace Newtonsoft.Json JsonToken numberType; bool singleDigit = (char.IsDigit(firstChar) && _stringReference.Length == 1); - bool nonBase10 = (firstChar == '0' && _stringReference.Length > 1 - && _stringReference.Chars[_stringReference.StartIndex + 1] != '.' - && _stringReference.Chars[_stringReference.StartIndex + 1] != 'e' - && _stringReference.Chars[_stringReference.StartIndex + 1] != 'E'); + bool nonBase10 = (firstChar == '0' && _stringReference.Length > 1 && _stringReference.Chars[_stringReference.StartIndex + 1] != '.' && _stringReference.Chars[_stringReference.StartIndex + 1] != 'e' && _stringReference.Chars[_stringReference.StartIndex + 1] != 'E'); - if (_readType == ReadType.ReadAsInt32) + if (readType == ReadType.ReadAsInt32) { if (singleDigit) { @@ -1288,9 +1559,7 @@ namespace Newtonsoft.Json try { - int integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) - ? Convert.ToInt32(number, 16) - : Convert.ToInt32(number, 8); + int integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt32(number, 16) : Convert.ToInt32(number, 8); numberValue = integer; } @@ -1319,12 +1588,12 @@ namespace Newtonsoft.Json numberType = JsonToken.Integer; } - else if (_readType == ReadType.ReadAsDecimal) + else if (readType == ReadType.ReadAsDecimal) { if (singleDigit) { // digit char values start at 48 - numberValue = (decimal)firstChar - 48; + numberValue = (decimal) firstChar - 48; } else if (nonBase10) { @@ -1333,9 +1602,7 @@ namespace Newtonsoft.Json try { // decimal.Parse doesn't support parsing hexadecimal values - long integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) - ? Convert.ToInt64(number, 16) - : Convert.ToInt64(number, 8); + long integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); numberValue = Convert.ToDecimal(integer); } @@ -1366,7 +1633,7 @@ namespace Newtonsoft.Json if (singleDigit) { // digit char values start at 48 - numberValue = (long)firstChar - 48; + numberValue = (long) firstChar - 48; numberType = JsonToken.Integer; } else if (nonBase10) @@ -1375,9 +1642,7 @@ namespace Newtonsoft.Json try { - numberValue = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) - ? Convert.ToInt64(number, 16) - : Convert.ToInt64(number, 8); + numberValue = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); } catch (Exception ex) { @@ -1463,7 +1728,7 @@ namespace Newtonsoft.Json } #endif - private void ParseComment() + private void ParseComment(bool setToken) { // should have already parsed / character before reaching this method _charPos++; @@ -1492,9 +1757,7 @@ namespace Newtonsoft.Json int initialPosition = _charPos; - bool commentFinished = false; - - while (!commentFinished) + while (true) { switch (_chars[_charPos]) { @@ -1508,8 +1771,8 @@ namespace Newtonsoft.Json throw JsonReaderException.Create(this, "Unexpected end while parsing comment."); } - _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition); - commentFinished = true; + EndComment(setToken, initialPosition, _charPos); + return; } } else @@ -1526,10 +1789,10 @@ namespace Newtonsoft.Json { if (_chars[_charPos] == '/') { - _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition - 1); + EndComment(setToken, initialPosition, _charPos - 1); _charPos++; - commentFinished = true; + return; } } } @@ -1537,16 +1800,16 @@ namespace Newtonsoft.Json case StringUtils.CarriageReturn: if (singlelineComment) { - _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition); - commentFinished = true; + EndComment(setToken, initialPosition, _charPos); + return; } ProcessCarriageReturn(true); break; case StringUtils.LineFeed: if (singlelineComment) { - _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition); - commentFinished = true; + EndComment(setToken, initialPosition, _charPos); + return; } ProcessLineFeed(); break; @@ -1555,10 +1818,14 @@ namespace Newtonsoft.Json break; } } + } - SetToken(JsonToken.Comment, _stringReference.ToString()); - - ClearRecentString(); + private void EndComment(bool setToken, int initialPosition, int endPosition) + { + if (setToken) + { + SetToken(JsonToken.Comment, new string(_chars, initialPosition, endPosition - initialPosition)); + } } private bool MatchValue(string value) diff --git a/Src/Newtonsoft.Json/Linq/JTokenReader.cs b/Src/Newtonsoft.Json/Linq/JTokenReader.cs index a5455f5b..ae9e6587 100644 --- a/Src/Newtonsoft.Json/Linq/JTokenReader.cs +++ b/Src/Newtonsoft.Json/Linq/JTokenReader.cs @@ -64,64 +64,12 @@ namespace Newtonsoft.Json.Linq } /// - /// Reads the next JSON token from the stream as a []. + /// Reads the next JSON token from the stream. /// /// - /// A [] or a null reference if the next JSON token is null. This method will return null at the end of an array. + /// true if the next token was read successfully; false if there are no more tokens to read. /// - public override byte[] ReadAsBytes() - { - return ReadAsBytesInternal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override decimal? ReadAsDecimal() - { - return ReadAsDecimalInternal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override int? ReadAsInt32() - { - return ReadAsInt32Internal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override string ReadAsString() - { - return ReadAsStringInternal(); - } - - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override DateTime? ReadAsDateTime() - { - return ReadAsDateTimeInternal(); - } - -#if !NET20 - /// - /// Reads the next JSON token from the stream as a . - /// - /// A . This method will return null at the end of an array. - public override DateTimeOffset? ReadAsDateTimeOffset() - { - return ReadAsDateTimeOffsetInternal(); - } -#endif - - internal override bool ReadInternal() + public override bool Read() { if (CurrentState != State.Start) { @@ -146,19 +94,6 @@ namespace Newtonsoft.Json.Linq return true; } - /// - /// Reads the next JSON token from the stream. - /// - /// - /// true if the next token was read successfully; false if there are no more tokens to read. - /// - public override bool Read() - { - _readType = ReadType.Read; - - return ReadInternal(); - } - private bool ReadOver(JToken t) { if (t == _root)