From 9021858293cd3b77b6ec5b9009b3fe9968c0470a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 5 Jan 2016 22:12:01 +1300 Subject: [PATCH] -Added ReadAsBoolean -Fixed reading booleans as strings -Fixed reading large integers as booleans --- .../JsonTextReaderTest.cs | 129 +++++++ Src/Newtonsoft.Json.Tests/PerformanceTests.cs | 4 + .../Serialization/JsonSerializerTest.cs | 108 ++++++ .../Serialization/TypeNameHandlingTests.cs | 2 +- Src/Newtonsoft.Json/JsonReader.cs | 65 +++- Src/Newtonsoft.Json/JsonTextReader.cs | 327 ++++++++++++------ Src/Newtonsoft.Json/JsonValidatingReader.cs | 12 + .../Serialization/JsonContract.cs | 4 + .../JsonSerializerInternalReader.cs | 3 + .../Serialization/TraceJsonReader.cs | 7 + Src/Newtonsoft.Json/Utilities/ConvertUtils.cs | 4 + 11 files changed, 564 insertions(+), 101 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index 428b99a0..45cab62c 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -2807,6 +2807,55 @@ new Date()")); Assert.IsTrue(reader.Read()); } + [Test] + public void ReadAsString_Boolean() + { + JsonTextReader reader = new JsonTextReader(new StringReader("{\"Test1\":false}")); + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + + string s = reader.ReadAsString(); + Assert.AreEqual("false", s); + + Assert.IsTrue(reader.Read()); + Assert.IsFalse(reader.Read()); + } + + [Test] + public void Read_Boolean_Failure() + { + JsonTextReader reader = new JsonTextReader(new StringReader("{\"Test1\":false1}")); + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + + ExceptionAssert.Throws( + () => { reader.Read(); }, + "Error parsing boolean value. Path 'Test1', line 1, position 14."); + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.IsFalse(reader.Read()); + } + + [Test] + public void ReadAsString_Boolean_Failure() + { + JsonTextReader reader = new JsonTextReader(new StringReader("{\"Test1\":false1}")); + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + + ExceptionAssert.Throws( + () => { reader.ReadAsString(); }, + "Unexpected character encountered while parsing value: 1. Path 'Test1', line 1, position 14."); + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.IsFalse(reader.Read()); + } + [Test] public void ParseConstructorWithBadCharacter() { @@ -3402,6 +3451,86 @@ null//comment Assert.AreEqual(JsonToken.EndArray, reader.TokenType); } +#if !(PORTABLE || PORTABLE40 || NET35 || NET20) + [Test] + public void ReadAsBoolean() + { + string json = @"[ + 1, + 0, + 1.1, + 0.0, + 0.000000000001, + 9999999999, + -9999999999, + 9999999999999999999999999999999999999999999999999999999999999999999999, + -9999999999999999999999999999999999999999999999999999999999999999999999, + 'true', + 'TRUE', + 'false', + 'FALSE', + '', + null +]"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual("", reader.Path); + + Assert.AreEqual(true, reader.ReadAsBoolean()); + Assert.AreEqual("[0]", reader.Path); + + Assert.AreEqual(false, reader.ReadAsBoolean()); + Assert.AreEqual("[1]", reader.Path); + + Assert.AreEqual(true, reader.ReadAsBoolean()); + Assert.AreEqual("[2]", reader.Path); + + Assert.AreEqual(false, reader.ReadAsBoolean()); + Assert.AreEqual("[3]", reader.Path); + + Assert.AreEqual(true, reader.ReadAsBoolean()); + Assert.AreEqual("[4]", reader.Path); + + Assert.AreEqual(true, reader.ReadAsBoolean()); + Assert.AreEqual("[5]", reader.Path); + + Assert.AreEqual(true, reader.ReadAsBoolean()); + Assert.AreEqual("[6]", reader.Path); + + Assert.AreEqual(true, reader.ReadAsBoolean()); + Assert.AreEqual("[7]", reader.Path); + + Assert.AreEqual(true, reader.ReadAsBoolean()); + Assert.AreEqual("[8]", reader.Path); + + Assert.AreEqual(true, reader.ReadAsBoolean()); + Assert.AreEqual("[9]", reader.Path); + + Assert.AreEqual(true, reader.ReadAsBoolean()); + Assert.AreEqual("[10]", reader.Path); + + Assert.AreEqual(false, reader.ReadAsBoolean()); + Assert.AreEqual("[11]", reader.Path); + + Assert.AreEqual(false, reader.ReadAsBoolean()); + Assert.AreEqual("[12]", reader.Path); + + Assert.AreEqual(null, reader.ReadAsBoolean()); + Assert.AreEqual("[13]", reader.Path); + + Assert.AreEqual(null, reader.ReadAsBoolean()); + Assert.AreEqual("[14]", reader.Path); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual("", reader.Path); + + Assert.IsFalse(reader.Read()); + Assert.AreEqual("", reader.Path); + } +#endif + [Test] public void ReadAsBytes() { diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index bcee95be..16f39448 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -67,8 +67,12 @@ namespace Newtonsoft.Json.Tests [TestFixture] public class PerformanceTests : TestFixtureBase { +#if DEBUG + public int Iterations = 1; +#else public int Iterations = 100; //public int Iterations = 10000; +#endif #region Data private const string BsonHex = diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index c9eca02d..4c32aad5 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -215,6 +215,114 @@ namespace Newtonsoft.Json.Tests.Serialization } #endif + [Test] + public void DeserializeBoolean_Null() + { + ExceptionAssert.Throws( + () => JsonConvert.DeserializeObject>(@"[null]"), + "Error converting value {null} to type 'System.Boolean'. Path '[0]', line 1, position 5."); + } + + [Test] + public void DeserializeBoolean_DateTime() + { + ExceptionAssert.Throws( + () => JsonConvert.DeserializeObject>(@"['2000-12-20T10:55:55Z']"), + "Could not convert string to boolean: 2000-12-20T10:55:55Z. Path '[0]', line 1, position 23."); + } + + [Test] + public void DeserializeBoolean_BadString() + { + ExceptionAssert.Throws( + () => JsonConvert.DeserializeObject>(@"['pie']"), + @"Could not convert string to boolean: pie. Path '[0]', line 1, position 6."); + } + + [Test] + public void DeserializeBoolean_EmptyString() + { + ExceptionAssert.Throws( + () => JsonConvert.DeserializeObject>(@"['']"), + @"Error converting value {null} to type 'System.Boolean'. Path '[0]', line 1, position 3."); + } + +#if !(PORTABLE || PORTABLE40 || NET35 || NET20) + [Test] + public void DeserializeBooleans() + { + IList l = JsonConvert.DeserializeObject>(@"[ + 1, + 0, + 1.1, + 0.0, + 0.000000000001, + 9999999999, + -9999999999, + 9999999999999999999999999999999999999999999999999999999999999999999999, + -9999999999999999999999999999999999999999999999999999999999999999999999, + 'true', + 'TRUE', + 'false', + 'FALSE' +]"); + + int i = 0; + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(false, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(false, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(false, l[i++]); + Assert.AreEqual(false, l[i++]); + } + + [Test] + public void DeserializeNullableBooleans() + { + IList l = JsonConvert.DeserializeObject>(@"[ + 1, + 0, + 1.1, + 0.0, + 0.000000000001, + 9999999999, + -9999999999, + 9999999999999999999999999999999999999999999999999999999999999999999999, + -9999999999999999999999999999999999999999999999999999999999999999999999, + 'true', + 'TRUE', + 'false', + 'FALSE', + '', + null +]"); + + int i = 0; + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(false, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(false, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(true, l[i++]); + Assert.AreEqual(false, l[i++]); + Assert.AreEqual(false, l[i++]); + Assert.AreEqual(null, l[i++]); + Assert.AreEqual(null, l[i++]); + } +#endif + [Test] public void CaseInsensitiveRequiredPropertyConstructorCreation() { diff --git a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs index f9d7a1c6..232f7b17 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs @@ -196,7 +196,7 @@ namespace Newtonsoft.Json.Tests.Serialization var reader = new JsonTextReader(new StringReader(sb.ToString())); var ser = new JsonSerializer(); ser.MetadataPropertyHandling = MetadataPropertyHandling.Default; - ser.Deserialize(reader); + ser.Deserialize(reader); }, "Unexpected token when deserializing primitive value: StartObject. Path '$value', line 1, position 11."); } diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index e00f41ae..8baf9e7d 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -27,13 +27,15 @@ using System; using System.Collections.Generic; using System.IO; using System.Globalization; +#if !(NET20 || NET35 || PORTABLE40 || PORTABLE) +using System.Numerics; +#endif using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Utilities; #if NET20 using Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; - #endif namespace Newtonsoft.Json @@ -646,6 +648,67 @@ 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 bool? ReadAsBoolean() + { + JsonToken t = GetContentToken(); + + switch (t) + { + case JsonToken.None: + case JsonToken.Null: + case JsonToken.EndArray: + return null; + case JsonToken.Integer: + case JsonToken.Float: + bool b; +#if !(NET20 || NET35 || PORTABLE40 || PORTABLE) + if (Value is BigInteger) + { + b = (BigInteger)Value != 0; + } + else +#endif + { + b = Convert.ToBoolean(Value, CultureInfo.InvariantCulture); + } + + SetToken(JsonToken.Boolean, b, false); + + return b; + case JsonToken.String: + return ReadBooleanString((string)Value); + case JsonToken.Boolean: + return (bool)Value; + } + + throw JsonReaderException.Create(this, "Error reading boolean. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); + } + + internal bool? ReadBooleanString(string s) + { + if (string.IsNullOrEmpty(s)) + { + SetToken(JsonToken.Null, null, false); + return null; + } + + bool b; + if (bool.TryParse(s, out b)) + { + SetToken(JsonToken.Boolean, b, false); + return b; + } + else + { + SetToken(JsonToken.String, s, false); + throw JsonReaderException.Create(this, "Could not convert string to boolean: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); + } + } + /// /// Reads the next JSON token from the stream as a . /// diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 14102dcf..c65a3772 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -30,6 +30,9 @@ using System.Runtime.CompilerServices; using System.Text; using System.IO; using System.Globalization; +#if !(PORTABLE || PORTABLE40 || NET35 || NET20) +using System.Numerics; +#endif using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json @@ -45,7 +48,8 @@ namespace Newtonsoft.Json #if !NET20 ReadAsDateTimeOffset, #endif - ReadAsDouble + ReadAsDouble, + ReadAsBoolean } /// @@ -129,81 +133,81 @@ namespace Newtonsoft.Json ReadStringIntoBuffer(quote); SetPostValueState(true); - if (readType == ReadType.ReadAsBytes) + switch (readType) { - Guid g; - byte[] data; - if (_stringReference.Length == 0) - { - data = new byte[0]; - } - else if (_stringReference.Length == 36 && ConvertUtils.TryConvertGuid(_stringReference.ToString(), out g)) - { - data = g.ToByteArray(); - } - else - { - data = Convert.FromBase64CharArray(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length); - } - - SetToken(JsonToken.Bytes, data, false); - } - 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) + case ReadType.ReadAsBytes: + Guid g; + byte[] data; + if (_stringReference.Length == 0) { - dateParseHandling = DateParseHandling.DateTime; + data = new byte[0]; } -#if !NET20 - else if (readType == ReadType.ReadAsDateTimeOffset) + else if (_stringReference.Length == 36 && ConvertUtils.TryConvertGuid(_stringReference.ToString(), out g)) { - dateParseHandling = DateParseHandling.DateTimeOffset; + data = g.ToByteArray(); } -#endif else { - dateParseHandling = _dateParseHandling; + data = Convert.FromBase64CharArray(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length); } - if (dateParseHandling == DateParseHandling.DateTime) + SetToken(JsonToken.Bytes, data, false); + break; + case ReadType.ReadAsString: + string text = _stringReference.ToString(); + + SetToken(JsonToken.String, text, false); + _quoteChar = quote; + break; + case ReadType.ReadAsInt32: + case ReadType.ReadAsDecimal: + case ReadType.ReadAsBoolean: + // caller will convert result + break; + default: + if (_dateParseHandling != DateParseHandling.None) { - DateTime dt; - if (DateTimeUtils.TryParseDateTime(_stringReference, DateTimeZoneHandling, DateFormatString, Culture, out dt)) + DateParseHandling dateParseHandling; + if (readType == ReadType.ReadAsDateTime) { - SetToken(JsonToken.Date, dt, false); - return; + dateParseHandling = DateParseHandling.DateTime; } - } #if !NET20 - else - { - DateTimeOffset dt; - if (DateTimeUtils.TryParseDateTimeOffset(_stringReference, DateFormatString, Culture, out dt)) + else if (readType == ReadType.ReadAsDateTimeOffset) { - SetToken(JsonToken.Date, dt, false); - return; + dateParseHandling = DateParseHandling.DateTimeOffset; } - } #endif - } + else + { + dateParseHandling = _dateParseHandling; + } - SetToken(JsonToken.String, _stringReference.ToString(), false); - _quoteChar = quote; + if (dateParseHandling == DateParseHandling.DateTime) + { + DateTime dt; + if (DateTimeUtils.TryParseDateTime(_stringReference, DateTimeZoneHandling, DateFormatString, Culture, out dt)) + { + SetToken(JsonToken.Date, dt, false); + return; + } + } +#if !NET20 + else + { + DateTimeOffset dt; + if (DateTimeUtils.TryParseDateTimeOffset(_stringReference, DateFormatString, Culture, out dt)) + { + SetToken(JsonToken.Date, dt, false); + return; + } + } +#endif + } + + SetToken(JsonToken.String, _stringReference.ToString(), false); + _quoteChar = quote; + break; } } @@ -488,15 +492,7 @@ namespace Newtonsoft.Json ParseComment(false); break; case ',': - _charPos++; - - if (_currentState != State.PostValue) - { - SetToken(JsonToken.Undefined); - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); - } - - SetStateBasedOnCurrent(); + ProcessValueComma(); break; case ']': _charPos++; @@ -505,7 +501,7 @@ namespace Newtonsoft.Json SetToken(JsonToken.EndArray); return null; } - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + throw CreateUnexpectedCharacterException(currentChar); case StringUtils.CarriageReturn: ProcessCarriageReturn(false); break; @@ -522,7 +518,7 @@ namespace Newtonsoft.Json if (!char.IsWhiteSpace(currentChar)) { - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + throw CreateUnexpectedCharacterException(currentChar); } // eat @@ -531,7 +527,6 @@ namespace Newtonsoft.Json } case State.Finished: ReadFinished(); - SetToken(JsonToken.None); return null; default: throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); @@ -605,12 +600,26 @@ namespace Newtonsoft.Json if (readType != ReadType.ReadAsString) { _charPos++; - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + throw CreateUnexpectedCharacterException(currentChar); } ParseNumber(ReadType.Read); string s = ((IFormattable)Value).ToString(null, CultureInfo.InvariantCulture); SetToken(JsonToken.String, s); return s; + case 't': + case 'f': + if (readType != ReadType.ReadAsString) + { + _charPos++; + throw CreateUnexpectedCharacterException(currentChar); + } + string expected = currentChar == 't' ? JsonConvert.True : JsonConvert.False; + if (!MatchValueWithTrailingSeparator(expected)) + { + throw CreateUnexpectedCharacterException(_chars[_charPos]); + } + SetToken(JsonToken.String, expected); + return expected; case 'n': HandleNull(); return null; @@ -618,15 +627,7 @@ namespace Newtonsoft.Json ParseComment(false); break; case ',': - _charPos++; - - if (_currentState != State.PostValue) - { - SetToken(JsonToken.Undefined); - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); - } - - SetStateBasedOnCurrent(); + ProcessValueComma(); break; case ']': _charPos++; @@ -635,7 +636,7 @@ namespace Newtonsoft.Json SetToken(JsonToken.EndArray); return null; } - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + throw CreateUnexpectedCharacterException(currentChar); case StringUtils.CarriageReturn: ProcessCarriageReturn(false); break; @@ -652,7 +653,7 @@ namespace Newtonsoft.Json if (!char.IsWhiteSpace(currentChar)) { - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + throw CreateUnexpectedCharacterException(currentChar); } // eat @@ -661,13 +662,148 @@ namespace Newtonsoft.Json } case State.Finished: ReadFinished(); - SetToken(JsonToken.None); return null; default: throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); } } + private JsonReaderException CreateUnexpectedCharacterException(char c) + { + return JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, c)); + } + + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override bool? ReadAsBoolean() + { + 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.Read); + return ReadBooleanString(_stringReference.ToString()); + 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.Read); + bool b; +#if !(NET20 || NET35 || PORTABLE40 || PORTABLE) + if (Value is BigInteger) + { + b = (BigInteger)Value != 0; + } + else +#endif + { + b = Convert.ToBoolean(Value, CultureInfo.InvariantCulture); + } + SetToken(JsonToken.Boolean, b, false); + return b; + case 't': + case 'f': + bool isTrue = currentChar == 't'; + string expected = isTrue ? JsonConvert.True : JsonConvert.False; + if (!MatchValueWithTrailingSeparator(expected)) + { + throw CreateUnexpectedCharacterException(_chars[_charPos]); + } + SetToken(JsonToken.Boolean, isTrue); + return isTrue; + case '/': + ParseComment(false); + break; + case ',': + ProcessValueComma(); + break; + case ']': + _charPos++; + if (_currentState == State.Array || _currentState == State.ArrayStart || _currentState == State.PostValue) + { + SetToken(JsonToken.EndArray); + return null; + } + throw CreateUnexpectedCharacterException(currentChar); + case StringUtils.CarriageReturn: + ProcessCarriageReturn(false); + break; + case StringUtils.LineFeed: + ProcessLineFeed(); + break; + case ' ': + case StringUtils.Tab: + // eat + _charPos++; + break; + default: + _charPos++; + + if (!char.IsWhiteSpace(currentChar)) + { + throw CreateUnexpectedCharacterException(currentChar); + } + + // eat + break; + } + } + case State.Finished: + ReadFinished(); + return null; + default: + throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); + } + } + + private void ProcessValueComma() + { + _charPos++; + + if (_currentState != State.PostValue) + { + SetToken(JsonToken.Undefined); + throw CreateUnexpectedCharacterException(','); + } + + SetStateBasedOnCurrent(); + } + private object ReadNumberValue(ReadType readType) { EnsureBuffer(); @@ -729,15 +865,7 @@ namespace Newtonsoft.Json ParseComment(false); break; case ',': - _charPos++; - - if (_currentState != State.PostValue) - { - SetToken(JsonToken.Undefined); - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); - } - - SetStateBasedOnCurrent(); + ProcessValueComma(); break; case ']': _charPos++; @@ -746,7 +874,7 @@ namespace Newtonsoft.Json SetToken(JsonToken.EndArray); return null; } - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + throw CreateUnexpectedCharacterException(currentChar); case StringUtils.CarriageReturn: ProcessCarriageReturn(false); break; @@ -763,7 +891,7 @@ namespace Newtonsoft.Json if (!char.IsWhiteSpace(currentChar)) { - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + throw CreateUnexpectedCharacterException(currentChar); } // eat @@ -772,7 +900,6 @@ namespace Newtonsoft.Json } case State.Finished: ReadFinished(); - SetToken(JsonToken.None); return null; default: throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); @@ -821,7 +948,7 @@ namespace Newtonsoft.Json } _charPos += 2; - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos - 1])); + throw CreateUnexpectedCharacterException(_chars[_charPos - 1]); } throw JsonReaderException.Create(this, "Unexpected end when reading JSON."); @@ -845,6 +972,8 @@ namespace Newtonsoft.Json throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); } } + + SetToken(JsonToken.None); } private bool ReadNullChar() @@ -1416,7 +1545,7 @@ namespace Newtonsoft.Json } else { - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); + throw CreateUnexpectedCharacterException(_chars[_charPos]); } } else @@ -1491,7 +1620,7 @@ namespace Newtonsoft.Json return true; } - throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); + throw CreateUnexpectedCharacterException(currentChar); } } } diff --git a/Src/Newtonsoft.Json/JsonValidatingReader.cs b/Src/Newtonsoft.Json/JsonValidatingReader.cs index d35cf4ba..260791b8 100644 --- a/Src/Newtonsoft.Json/JsonValidatingReader.cs +++ b/Src/Newtonsoft.Json/JsonValidatingReader.cs @@ -441,6 +441,18 @@ namespace Newtonsoft.Json return d; } + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . + public override bool? ReadAsBoolean() + { + bool? b = _reader.ReadAsBoolean(); + + ValidateCurrentToken(); + return b; + } + /// /// Reads the next JSON token from the stream as a . /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index befe9e94..a9f885d1 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -333,6 +333,10 @@ namespace Newtonsoft.Json.Serialization { InternalReadType = ReadType.ReadAsDecimal; } + else if (NonNullableUnderlyingType == typeof(bool)) + { + InternalReadType = ReadType.ReadAsBoolean; + } else if (NonNullableUnderlyingType == typeof(string)) { InternalReadType = ReadType.ReadAsString; diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 5da4476f..7864da5c 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -2216,6 +2216,9 @@ namespace Newtonsoft.Json.Serialization case ReadType.ReadAsBytes: reader.ReadAsBytes(); break; + case ReadType.ReadAsBoolean: + reader.ReadAsBoolean(); + break; case ReadType.ReadAsString: reader.ReadAsString(); break; diff --git a/Src/Newtonsoft.Json/Serialization/TraceJsonReader.cs b/Src/Newtonsoft.Json/Serialization/TraceJsonReader.cs index d9ea487b..78b389e3 100644 --- a/Src/Newtonsoft.Json/Serialization/TraceJsonReader.cs +++ b/Src/Newtonsoft.Json/Serialization/TraceJsonReader.cs @@ -71,6 +71,13 @@ namespace Newtonsoft.Json.Serialization return value; } + public override bool? ReadAsBoolean() + { + var value = _innerReader.ReadAsBoolean(); + _textWriter.WriteToken(_innerReader, false, false, true); + return value; + } + public override DateTime? ReadAsDateTime() { var value = _innerReader.ReadAsDateTime(); diff --git a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs index 4749eb0b..227db86d 100644 --- a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs @@ -376,6 +376,10 @@ namespace Newtonsoft.Json.Utilities { return (ulong)i; } + if (targetType == typeof(bool)) + { + return i != 0; + } try {