From faf6426527b8ad0ba96653f086ad3e2f4de7e37c Mon Sep 17 00:00:00 2001 From: JamesNK Date: Mon, 16 Jan 2012 06:53:41 +0000 Subject: [PATCH] -Rewrote JsonTextReader to use internal buffer to improve performance -Improved JsonSerializer to cache contract/converters where possible to improve performance -Improved JsonTextReader error message when additional JSON content found -Fixed internal exception thrown when deserializing Decimal/DateTimeOffset/Byte lists -Fixed various JsonTextReader bugs --- Src/Newtonsoft.Json.Tests/JsonConvertTest.cs | 2 +- .../JsonTextReaderTest.cs | 951 +++++++++++++- .../JsonValidatingReaderTests.cs | 22 +- Src/Newtonsoft.Json.Tests/Linq/JArrayTests.cs | 2 +- .../Linq/JObjectTests.cs | 2 +- .../Linq/JTokenReaderTest.cs | 12 +- Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../Schema/ExtensionsTests.cs | 2 +- .../Serialization/JsonSerializerTest.cs | 41 +- Src/Newtonsoft.Json/Bson/BsonReader.cs | 11 +- Src/Newtonsoft.Json/JsonConvert.cs | 2 +- Src/Newtonsoft.Json/JsonReader.cs | 49 +- Src/Newtonsoft.Json/JsonTextReader.cs | 1108 +++++++++++------ Src/Newtonsoft.Json/JsonValidatingReader.cs | 2 +- Src/Newtonsoft.Json/Linq/JToken.cs | 1 - Src/Newtonsoft.Json/Linq/JTokenReader.cs | 9 + .../Newtonsoft.Json.Net20.csproj | 1 + .../Newtonsoft.Json.Net35.csproj | 1 + .../Newtonsoft.Json.Silverlight.csproj | 1 + .../Newtonsoft.Json.WindowsPhone.csproj | 1 + Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 1 + .../Properties/AssemblyInfo.cs | 4 +- .../Schema/JsonSchemaGenerator.cs | 16 +- .../Serialization/JsonArrayContract.cs | 1 + .../Serialization/JsonDictionaryContract.cs | 2 + .../Serialization/JsonProperty.cs | 3 + .../JsonSerializerInternalReader.cs | 139 ++- .../JsonSerializerInternalWriter.cs | 31 +- .../Serialization/JsonStringContract.cs | 2 +- Src/Newtonsoft.Json/Utilities/StringBuffer.cs | 12 +- .../Utilities/StringReference.cs | 41 + 32 files changed, 1971 insertions(+), 509 deletions(-) create mode 100644 Src/Newtonsoft.Json/Utilities/StringReference.cs diff --git a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs index 750b100f..91c11b9a 100644 --- a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs @@ -257,7 +257,7 @@ now brown cow?", '"', true); } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "After parsing a value an unexpected character was encountered: t. Line 1, position 20.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Additional text encountered after finished reading JSON content: t. Line 1, position 19.")] public void TestInvalidStrings() { string orig = @"this is a string ""that has quotes"" "; diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index 9b3ef943..0076a546 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -31,6 +31,7 @@ using NUnit.Framework; using Newtonsoft.Json; using System.IO; using System.Xml; +using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Tests { @@ -87,6 +88,108 @@ namespace Newtonsoft.Json.Tests } } + [Test] + public void ReadConstructor() + { + string json = @"{""DefaultConverter"":new Date(0, ""hi""),""MemberConverter"":""1970-01-01T00:00:00Z""}"; + + JsonReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartConstructor, reader.TokenType); + Assert.AreEqual("Date", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(0, reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual("hi", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndConstructor, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual("MemberConverter", reader.Value); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Additional text encountered after finished reading JSON content: ,. Line 5, position 2.")] + public void ParseAdditionalContent_Comma() + { + string json = @"[ +""Small"", +""Medium"", +""Large"" +],"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + while (reader.Read()) + { + } + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Additional text encountered after finished reading JSON content: c. Line 5, position 2.")] + public void ParseAdditionalContent_Text() + { + string json = @"[ +""Small"", +""Medium"", +""Large"" +]content"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.SetCharBuffer(new char[2]); + + reader.Read(); + Assert.AreEqual(1, reader.LineNumber); + + reader.Read(); + Assert.AreEqual(2, reader.LineNumber); + + reader.Read(); + Assert.AreEqual(3, reader.LineNumber); + + reader.Read(); + Assert.AreEqual(4, reader.LineNumber); + + reader.Read(); + Assert.AreEqual(5, reader.LineNumber); + + reader.Read(); + } + + [Test] + public void ParseAdditionalContent_Whitespace() + { + string json = @"[ +""Small"", +""Medium"", +""Large"" +] + +"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + while (reader.Read()) + { + } + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Additional text encountered after finished reading JSON content: a. Line 1, position 5.")] + public void ParseAdditionalContent_WhitespaceThenText() + { + string json = @"'hi' a"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + while (reader.Read()) + { + } + } + [Test] public void ReadingIndented() { @@ -102,6 +205,8 @@ namespace Newtonsoft.Json.Tests using (JsonTextReader jsonReader = new JsonTextReader(sr)) { + jsonReader.SetCharBuffer(new char[5]); + Assert.AreEqual(jsonReader.TokenType, JsonToken.None); Assert.AreEqual(0, jsonReader.LineNumber); Assert.AreEqual(0, jsonReader.LinePosition); @@ -115,48 +220,48 @@ namespace Newtonsoft.Json.Tests Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); Assert.AreEqual(jsonReader.Value, "CPU"); Assert.AreEqual(2, jsonReader.LineNumber); - Assert.AreEqual(6, jsonReader.LinePosition); + Assert.AreEqual(7, jsonReader.LinePosition); jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.String); - Assert.AreEqual(jsonReader.Value, "Intel"); + Assert.AreEqual(JsonToken.String, jsonReader.TokenType); + Assert.AreEqual("Intel", jsonReader.Value); Assert.AreEqual(2, jsonReader.LineNumber); - Assert.AreEqual(14, jsonReader.LinePosition); + Assert.AreEqual(15, jsonReader.LinePosition); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); Assert.AreEqual(jsonReader.Value, "Drives"); Assert.AreEqual(3, jsonReader.LineNumber); - Assert.AreEqual(9, jsonReader.LinePosition); + Assert.AreEqual(10, jsonReader.LinePosition); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.StartArray); Assert.AreEqual(3, jsonReader.LineNumber); - Assert.AreEqual(11, jsonReader.LinePosition); + Assert.AreEqual(12, jsonReader.LinePosition); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.String); Assert.AreEqual(jsonReader.Value, "DVD read/writer"); Assert.AreEqual(jsonReader.QuoteChar, '\''); Assert.AreEqual(4, jsonReader.LineNumber); - Assert.AreEqual(21, jsonReader.LinePosition); + Assert.AreEqual(22, jsonReader.LinePosition); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.String); Assert.AreEqual(jsonReader.Value, "500 gigabyte hard drive"); Assert.AreEqual(jsonReader.QuoteChar, '"'); Assert.AreEqual(5, jsonReader.LineNumber); - Assert.AreEqual(29, jsonReader.LinePosition); + Assert.AreEqual(30, jsonReader.LinePosition); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.EndArray); Assert.AreEqual(6, jsonReader.LineNumber); - Assert.AreEqual(3, jsonReader.LinePosition); + Assert.AreEqual(4, jsonReader.LinePosition); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.EndObject); Assert.AreEqual(7, jsonReader.LineNumber); - Assert.AreEqual(1, jsonReader.LinePosition); + Assert.AreEqual(2, jsonReader.LinePosition); Assert.IsFalse(jsonReader.Read()); } @@ -165,7 +270,11 @@ namespace Newtonsoft.Json.Tests [Test] public void Depth() { - string input = "{value:'Purple\\r \\n monkey\\'s:\\tdishwasher',array:[1,2]}"; + string input = @"{ + value:'Purple', + array:[1,2], + subobject:{prop:1} +}"; StringReader sr = new StringReader(input); @@ -183,7 +292,7 @@ namespace Newtonsoft.Json.Tests jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.String); - Assert.AreEqual(jsonReader.Value, "Purple\r \n monkey's:\tdishwasher"); + Assert.AreEqual(jsonReader.Value, @"Purple"); Assert.AreEqual(jsonReader.QuoteChar, '\''); Assert.AreEqual(1, jsonReader.Depth); @@ -193,22 +302,42 @@ namespace Newtonsoft.Json.Tests jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.StartArray); - Assert.AreEqual(2, jsonReader.Depth); + Assert.AreEqual(1, jsonReader.Depth); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.Integer); Assert.AreEqual(1, jsonReader.Value); - Assert.AreEqual(3, jsonReader.Depth); + Assert.AreEqual(2, jsonReader.Depth); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.Integer); Assert.AreEqual(2, jsonReader.Value); - Assert.AreEqual(3, jsonReader.Depth); + Assert.AreEqual(2, jsonReader.Depth); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.EndArray); Assert.AreEqual(1, jsonReader.Depth); + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(1, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.StartObject); + Assert.AreEqual(1, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(2, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.Integer); + Assert.AreEqual(2, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.EndObject); + Assert.AreEqual(1, jsonReader.Depth); + jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.EndObject); Assert.AreEqual(0, jsonReader.Depth); @@ -231,6 +360,154 @@ Parameter name: reader")] reader.Read(); } + [Test] + public void ReadLongString() + { + string s = new string('a', 10000); + JsonReader reader = new JsonTextReader(new StringReader("'" + s + "'")); + reader.Read(); + + Assert.AreEqual(s, reader.Value); + } + + [Test] + public void ReadLongJsonArray() + { + int valueCount = 10000; + StringWriter sw = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(sw); + writer.WriteStartArray(); + for (int i = 0; i < valueCount; i++) + { + writer.WriteValue(i); + } + writer.WriteEndArray(); + + string json = sw.ToString(); + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + Assert.IsTrue(reader.Read()); + for (int i = 0; i < valueCount; i++) + { + Assert.IsTrue(reader.Read()); + Assert.AreEqual(i, reader.Value); + } + Assert.IsTrue(reader.Read()); + Assert.IsFalse(reader.Read()); + } + + [Test] + public void NullCharReading() + { + string json = "\0{\0'\0h\0i\0'\0:\0[\01\0,\0'\0'\0\0,\0null\0]\0,\0do\0:true\0}\0\0/*\0sd\0f\0*/\0/*\0sd\0f\0*/ \0"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Integer, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.String, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Null, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Boolean, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + Assert.AreEqual("\0sd\0f\0", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + Assert.AreEqual("\0sd\0f\0", reader.Value); + + Assert.IsFalse(reader.Read()); + } + + [Test] + public void AppendCharsWhileReadingNull() + { + string json = @"[ + { + ""$id"": ""1"", + ""Name"": ""e1"", + ""Manager"": null + }, + { + ""$id"": ""2"", + ""Name"": ""e2"", + ""Manager"": null + }, + { + ""$ref"": ""1"" + }, + { + ""$ref"": ""2"" + } +]"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.SetCharBuffer(new char[129]); + + for (int i = 0; i < 15; i++) + { + reader.Read(); + } + + reader.Read(); + Assert.AreEqual(JsonToken.Null, reader.TokenType); + } + + [Test] + public void AppendCharsWhileReadingNewLine() + { + string json = @" +{ + ""description"": ""A person"", + ""type"": ""object"", + ""properties"": + { + ""name"": {""type"":""string""}, + ""hobbies"": { + ""type"": ""array"", + ""items"": {""type"":""string""} + } + } +} +"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.SetCharBuffer(new char[129]); + + for (int i = 0; i < 14; i++) + { + Assert.IsTrue(reader.Read()); + } + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + Assert.AreEqual("type", reader.Value); + } + [Test] public void ReadNullTerminatorStrings() { @@ -241,10 +518,10 @@ Parameter name: reader")] } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected end while parsing unicode character. Line 1, position 7.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected end while parsing unicode character. Line 1, position 4.")] public void UnexpectedEndOfHex() { - JsonReader reader = new JsonTextReader(new StringReader(@"'h\u006")); + JsonReader reader = new JsonTextReader(new StringReader(@"'h\u123")); reader.Read(); } @@ -293,6 +570,97 @@ Parameter name: reader")] reader.Read(); } + [Test] + public void ReadNewLines() + { + string newLinesText = StringUtils.CarriageReturn + StringUtils.CarriageReturnLineFeed + StringUtils.LineFeed + StringUtils.CarriageReturnLineFeed + " " + StringUtils.CarriageReturn + StringUtils.CarriageReturnLineFeed; + + string json = + newLinesText + + "{" + newLinesText + + "'" + newLinesText + + "name1" + newLinesText + + "'" + newLinesText + + ":" + newLinesText + + "[" + newLinesText + + "new" + newLinesText + + "Date" + newLinesText + + "(" + newLinesText + + "1" + newLinesText + + "," + newLinesText + + "null" + newLinesText + + "/*" + newLinesText + + "blah comment" + newLinesText + + "*/" + newLinesText + + ")" + newLinesText + + "," + newLinesText + + "1.1111" + newLinesText + + "]" + newLinesText + + "," + newLinesText + + "name2" + newLinesText + + ":" + newLinesText + + "{" + newLinesText + + "}" + newLinesText + + "}" + newLinesText; + + int count = 0; + StringReader sr = new StringReader(newLinesText); + while (sr.ReadLine() != null) + { + count++; + } + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(7, reader.LineNumber); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(31, reader.LineNumber); + Assert.AreEqual(newLinesText + "name1" + newLinesText, reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(37, reader.LineNumber); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(55, reader.LineNumber); + Assert.AreEqual(JsonToken.StartConstructor, reader.TokenType); + Assert.AreEqual("Date", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(61, reader.LineNumber); + Assert.AreEqual(1, reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(73, reader.LineNumber); + Assert.AreEqual(null, reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(91, reader.LineNumber); + Assert.AreEqual(newLinesText + "blah comment" + newLinesText, reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(97, reader.LineNumber); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(109, reader.LineNumber); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(115, reader.LineNumber); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(133, reader.LineNumber); + Assert.AreEqual("name2", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(139, reader.LineNumber); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(145, reader.LineNumber); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(151, reader.LineNumber); + } + [Test] public void ParsingQuotedPropertyWithControlCharacters() { @@ -303,7 +671,10 @@ Parameter name: reader")] Assert.AreEqual(@"hi bye", reader.Value); Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Integer, reader.TokenType); + Assert.AreEqual(1, reader.Value); Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); Assert.IsFalse(reader.Read()); } @@ -361,21 +732,21 @@ bye", reader.Value); Assert.AreEqual(0, jsonReader.Depth); jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.StartObject); + Assert.AreEqual(JsonToken.StartObject, jsonReader.TokenType); Assert.AreEqual(0, jsonReader.Depth); - + jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(JsonToken.PropertyName, jsonReader.TokenType); Assert.AreEqual(1, jsonReader.Depth); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.String); - Assert.AreEqual(jsonReader.Value, "Purple\r \n monkey's:\tdishwasher"); - Assert.AreEqual(jsonReader.QuoteChar, '\''); + Assert.AreEqual("Purple\r \n monkey's:\tdishwasher", jsonReader.Value); + Assert.AreEqual('\'', jsonReader.QuoteChar); Assert.AreEqual(1, jsonReader.Depth); jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.EndObject); + Assert.AreEqual(JsonToken.EndObject, jsonReader.TokenType); Assert.AreEqual(0, jsonReader.Depth); } } @@ -394,13 +765,48 @@ bye", reader.Value); object o = JsonConvert.DeserializeObject(input); } + [Test] + public void ReadRandomJson() + { + string json = @"[ + true, + { + ""integer"": 99, + ""string"": ""how now brown cow?"", + ""array"": [ + 0, + 1, + 2, + 3, + 4, + { + ""decimal"": 990.00990099 + }, + 5 + ] + }, + ""This is a string."", + null, + null +]"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + while (reader.Read()) + { + + } + } + [Test] public void WriteReadWrite() { StringBuilder sb = new StringBuilder(); StringWriter sw = new StringWriter(sb); - using (JsonWriter jsonWriter = new JsonTextWriter(sw)) + using (JsonWriter jsonWriter = new JsonTextWriter(sw) + { + Formatting = Formatting.Indented + }) { jsonWriter.WriteStartArray(); jsonWriter.WriteValue(true); @@ -443,9 +849,12 @@ bye", reader.Value); sb = new StringBuilder(); sw = new StringWriter(sb); - using (JsonWriter jsonWriter = new JsonTextWriter(sw)) + using (JsonWriter jsonWriter = new JsonTextWriter(sw) + { + Formatting = Formatting.Indented + }) { - serializer.Serialize(sw, jsonObject); + serializer.Serialize(jsonWriter, jsonObject); } Assert.AreEqual(json, sb.ToString()); @@ -513,6 +922,7 @@ bye", reader.Value); string json = @"[""\u003c"",""\u5f20""]"; JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.SetCharBuffer(new char[2]); reader.Read(); Assert.AreEqual(JsonToken.StartArray, reader.TokenType); @@ -609,9 +1019,7 @@ bye", reader.Value); { string json = @"{ ""A"" : true, - ""B"" ""hello"", // Notice the colon is missing - ""C"" : ""bye"" -}"; + ""B"" """; JsonTextReader reader = new JsonTextReader(new StringReader(json)); @@ -667,12 +1075,119 @@ bye", reader.Value); Assert.IsFalse(jsonReader.Read()); } + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected character encountered while parsing value: }. Line 1, position 1.")] + public void ReadBadCharInArray() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"[}")); + + reader.Read(); + reader.Read(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected end when reading decimal: Line 0, position 0.")] + public void ReadAsDecimalNoContent() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"")); + + reader.ReadAsDecimal(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected end when reading bytes: Line 0, position 0.")] + public void ReadAsBytesNoContent() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"")); + + reader.ReadAsBytes(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected end when reading bytes: Line 1, position 1.")] + public void ReadAsBytesNoContentWrappedObject() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"{")); + + reader.ReadAsBytes(); + } + +#if !NET20 + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected end when reading date: Line 0, position 0.")] + public void ReadAsDateTimeOffsetNoContent() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"")); + + reader.ReadAsDateTimeOffset(); + } +#endif + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected token when reading decimal: StartConstructor. Line 1, position 9.")] + public void ReadAsDecimalBadContent() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"new Date()")); + + reader.ReadAsDecimal(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected token when reading bytes: StartConstructor. Line 1, position 9.")] + public void ReadAsBytesBadContent() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"new Date()")); + + reader.ReadAsBytes(); + } + +#if !NET20 + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected token when reading date: StartConstructor. Line 1, position 9.")] + public void ReadAsDateTimeOffsetBadContent() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"new Date()")); + + reader.ReadAsDateTimeOffset(); + } +#endif + + [Test] + public void ReadAsBytesIntegerArrayWithComments() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"[/*hi*/1/*hi*/,2/*hi*/]")); + + byte[] data = reader.ReadAsBytes(); + Assert.AreEqual(2, data.Length); + Assert.AreEqual(1, data[0]); + Assert.AreEqual(2, data[1]); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected end when reading bytes: Line 1, position 2.")] + public void ReadAsBytesIntegerArrayWithNoEnd() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"[1")); + + reader.ReadAsBytes(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected token when reading bytes: Float. Line 1, position 4.")] + public void ReadAsBytesArrayWithBadContent() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"[1.0]")); + + reader.ReadAsBytes(); + } + [Test] public void ReadUnicode() { string json = @"{""Message"":""Hi,I\u0092ve send you smth""}"; JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.SetCharBuffer(new char[5]); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.StartObject, reader.TokenType); @@ -801,6 +1316,46 @@ bye", reader.Value); Assert.AreEqual(JsonToken.EndObject, reader.TokenType); } + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error parsing null value. Line 0, position 0.")] + public void MatchWithInsufficentCharacters() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"nul")); + reader.Read(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error parsing null value. Line 0, position 0.")] + public void MatchWithWrongCharacters() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"nulz")); + reader.Read(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error parsing null value. Line 1, position 4.")] + public void MatchWithNoTrailingSeperator() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"nullz")); + reader.Read(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected end while parsing comment.")] + public void UnclosedComment() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"/* sdf")); + reader.Read(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error parsing comment. Expected: *, got s. Line 1, position 1.")] + public void BadCommentStart() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"/sdf")); + reader.Read(); + } + [Test] public void ReadAsDateTimeOffsetIsoDate() { @@ -918,5 +1473,343 @@ bye", reader.Value); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.EndObject, reader.TokenType); } + + [Test] + public void ReadBufferOnControlChar() + { + string json = @"[ + { + ""Name"": ""Jim"", + ""BirthDate"": ""\/Date(978048000000)\/"", + ""LastModified"": ""\/Date(978048000000)\/"" + }, + { + ""Name"": ""Jim"", + ""BirthDate"": ""\/Date(978048000000)\/"", + ""LastModified"": ""\/Date(978048000000)\/"" + } +]"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.SetCharBuffer(new char[5]); + for (int i = 0; i < 13; i++) + { + reader.Read(); + } + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(new DateTime(631136448000000000), reader.Value); + } + + [Test] + public void ReadBufferOnEndComment() + { + string json = @"/*comment*/ { /*comment*/ + ""Name"": /*comment*/ ""Apple"" /*comment*/, /*comment*/ + ""ExpiryDate"": ""\/Date(1230422400000)\/"", + ""Price"": 3.99, + ""Sizes"": /*comment*/ [ /*comment*/ + ""Small"", /*comment*/ + ""Medium"" /*comment*/, + /*comment*/ ""Large"" + /*comment*/ ] /*comment*/ + } /*comment*/"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.SetCharBuffer(new char[5]); + + for (int i = 0; i < 26; i++) + { + Assert.IsTrue(reader.Read()); + } + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsFalse(reader.Read()); + } + + [Test] + public void ParseNullStringConstructor() + { + string json = "new Date\0()"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.SetCharBuffer(new char[7]); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual("Date", reader.Value); + Assert.AreEqual(JsonToken.StartConstructor, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndConstructor, reader.TokenType); + } + + [Test] + public void ParseLineFeedDelimitedConstructor() + { + string json = "new Date\n()"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual("Date", reader.Value); + Assert.AreEqual(JsonToken.StartConstructor, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndConstructor, reader.TokenType); + } + + [Test] + public void ParseArrayWithMissingValues() + { + string json = "[,,, \n\r\n \0 \r , , ]"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Undefined, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Undefined, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Undefined, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Undefined, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Undefined, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndArray, reader.TokenType); + } + + [Test] + public void ParseBooleanWithNoExtraContent() + { + string json = "[true "; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.IsFalse(reader.Read()); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected end while parsing constructor.")] + public void ParseConstructorWithUnexpectedEnd() + { + string json = "new Dat"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + reader.Read(); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected character while parsing constructor: !. Line 1, position 9.")] + public void ParseConstructorWithUnexpectedCharacter() + { + string json = "new Date !"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + reader.Read(); + } + + [Test] + public void ParseObjectWithNoEnd() + { + string json = "{hi:1, "; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.IsFalse(reader.Read()); + } + + [Test] + public void ParseEmptyArray() + { + string json = "[]"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndArray, reader.TokenType); + } + + [Test] + public void ParseEmptyObject() + { + string json = "{}"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error parsing boolean value. Line 1, position 4.")] + public void ParseIncompleteCommentSeperator() + { + JsonTextReader reader = new JsonTextReader(new StringReader("true/")); + + reader.Read(); + } + + [Test] + public void ParseEmptyConstructor() + { + string json = "new Date()"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartConstructor, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndConstructor, reader.TokenType); + } + + [Test] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected character while parsing constructor: ,. Line 1, position 8.")] + public void ParseConstructorWithBadCharacter() + { + string json = "new Date,()"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + } + + [Test] + public void ParseContentDelimitedByNonStandardWhitespace() + { + string json = "\x00a0{\x00a0'h\x00a0i\x00a0'\x00a0:\x00a0[\x00a0true\x00a0,\x00a0new\x00a0Date\x00a0(\x00a0)\x00a0]\x00a0/*\x00a0comment\x00a0*/\x00a0}\x00a0"; + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Boolean, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartConstructor, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndConstructor, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + + Assert.IsFalse(reader.Read()); + } + + [Test] + public void ReadContentDelimitedByComments() + { + string json = @"/*comment*/{/*comment*/Name:/*comment*/true/*comment*/,/*comment*/ + ""ExpiryDate"":/*comment*/new +" + StringUtils.LineFeed + +@"Date +(/*comment*/null/*comment*/), + ""Price"": 3.99, + ""Sizes"":/*comment*/[/*comment*/ + ""Small""/*comment*/]/*comment*/}/*comment*/"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + Assert.AreEqual("Name", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Boolean, reader.TokenType); + Assert.AreEqual(true, reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + Assert.AreEqual("ExpiryDate", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartConstructor, reader.TokenType); + Assert.AreEqual(5, reader.LineNumber); + Assert.AreEqual("Date", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Null, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Comment, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndConstructor, reader.TokenType); + } + + [Test] + public void ParseOctalNumber() + { + string json = @"010"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + reader.ReadAsDecimal(); + Assert.AreEqual(JsonToken.Float, reader.TokenType); + Assert.AreEqual(8m, reader.Value); + } + + [Test] + public void ParseHexNumber() + { + string json = @"0x20"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + reader.ReadAsDecimal(); + Assert.AreEqual(JsonToken.Float, reader.TokenType); + Assert.AreEqual(32m, reader.Value); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/JsonValidatingReaderTests.cs b/Src/Newtonsoft.Json.Tests/JsonValidatingReaderTests.cs index 432cf9b7..a0ec4842 100644 --- a/Src/Newtonsoft.Json.Tests/JsonValidatingReaderTests.cs +++ b/Src/Newtonsoft.Json.Tests/JsonValidatingReaderTests.cs @@ -371,7 +371,7 @@ namespace Newtonsoft.Json.Tests Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Integer, reader.TokenType); - Assert.AreEqual(@"Value 3 is not defined in enum. Line 1, position 7.", validationEventArgs.Message); + Assert.AreEqual(@"Value 3 is not defined in enum. Line 1, position 6.", validationEventArgs.Message); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.EndArray, reader.TokenType); @@ -457,7 +457,7 @@ namespace Newtonsoft.Json.Tests Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Float, reader.TokenType); - Assert.AreEqual(@"Value 3.0 is not defined in enum. Line 1, position 13.", validationEventArgs.Message); + Assert.AreEqual(@"Value 3.0 is not defined in enum. Line 1, position 12.", validationEventArgs.Message); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.EndArray, reader.TokenType); @@ -496,7 +496,7 @@ namespace Newtonsoft.Json.Tests Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Float, reader.TokenType); - Assert.AreEqual(@"Float 4.001 is not evenly divisible by 0.1. Line 1, position 15.", validationEventArgs.Message); + Assert.AreEqual(@"Float 4.001 is not evenly divisible by 0.1. Line 1, position 14.", validationEventArgs.Message); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.EndArray, reader.TokenType); @@ -723,7 +723,7 @@ namespace Newtonsoft.Json.Tests Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Float, reader.TokenType); - Assert.AreEqual(@"Type Float is disallowed. Line 1, position 11.", validationEventArgs.Message); + Assert.AreEqual(@"Type Float is disallowed. Line 1, position 10.", validationEventArgs.Message); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.EndArray, reader.TokenType); @@ -1029,7 +1029,7 @@ namespace Newtonsoft.Json.Tests Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.Integer, reader.TokenType); - Assert.AreEqual("Invalid type. Expected String but got Integer. Line 1, position 3.", validationEventArgs.Message); + Assert.AreEqual("Invalid type. Expected String but got Integer. Line 1, position 2.", validationEventArgs.Message); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.String, reader.TokenType); @@ -1097,7 +1097,7 @@ namespace Newtonsoft.Json.Tests Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); - Assert.AreEqual("Property 'hey' has not been defined and the schema does not allow additional properties. Line 5, position 8.", validationEventArgs.Message); + Assert.AreEqual("Property 'hey' has not been defined and the schema does not allow additional properties. Line 5, position 9.", validationEventArgs.Message); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.String, reader.TokenType); @@ -1198,7 +1198,7 @@ namespace Newtonsoft.Json.Tests Assert.AreEqual(JsonToken.String, reader.TokenType); Assert.AreEqual("secasecasecasecaseca", reader.Value.ToString()); Assert.AreEqual(1, errors.Count); - Assert.AreEqual("String 'secasecasecasecaseca' exceeds maximum length of 10. Line 3, position 41.", errors[0]); + Assert.AreEqual("String 'secasecasecasecaseca' exceeds maximum length of 10. Line 3, position 42.", errors[0]); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); @@ -1218,9 +1218,9 @@ namespace Newtonsoft.Json.Tests Assert.AreEqual(JsonToken.String, reader.TokenType); Assert.AreEqual("aaa", reader.Value.ToString()); Assert.AreEqual(4, errors.Count); - Assert.AreEqual("String 'aaa' is less than minimum length of 7. Line 5, position 39.", errors[1]); - Assert.AreEqual("String 'aaa' does not match regex pattern 'hi'. Line 5, position 39.", errors[2]); - Assert.AreEqual("String 'aaa' does not match regex pattern 'hi2u'. Line 5, position 39.", errors[3]); + Assert.AreEqual("String 'aaa' is less than minimum length of 7. Line 5, position 40.", errors[1]); + Assert.AreEqual("String 'aaa' does not match regex pattern 'hi'. Line 5, position 40.", errors[2]); + Assert.AreEqual("String 'aaa' does not match regex pattern 'hi2u'. Line 5, position 40.", errors[3]); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); @@ -1231,7 +1231,7 @@ namespace Newtonsoft.Json.Tests Assert.AreEqual(JsonToken.String, reader.TokenType); Assert.AreEqual("three", reader.Value.ToString()); Assert.AreEqual(5, errors.Count); - Assert.AreEqual("String 'three' is less than minimum length of 6. Line 6, position 24.", errors[4]); + Assert.AreEqual("String 'three' is less than minimum length of 6. Line 6, position 25.", errors[4]); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.EndObject, reader.TokenType); diff --git a/Src/Newtonsoft.Json.Tests/Linq/JArrayTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JArrayTests.cs index 030a8fdb..a25c7fc5 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JArrayTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JArrayTests.cs @@ -450,7 +450,7 @@ Parameter name: index")] } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected state: Finished. Line 5, position 3.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Additional text encountered after finished reading JSON content: ,. Line 5, position 2.")] public void ParseAdditionalContent() { string json = @"[ diff --git a/Src/Newtonsoft.Json.Tests/Linq/JObjectTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JObjectTests.cs index 959daecd..c8060c06 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JObjectTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JObjectTests.cs @@ -1626,7 +1626,7 @@ Parameter name: arrayIndex")] } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected state: Finished. Line 10, position 3.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Additional text encountered after finished reading JSON content: ,. Line 10, position 2.")] public void ParseAdditionalContent() { string json = @"{ diff --git a/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs b/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs index 9a0df42c..aee77d7b 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs @@ -147,41 +147,41 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); Assert.AreEqual(jsonReader.Value, "CPU"); Assert.AreEqual(2, lineInfo.LineNumber); - Assert.AreEqual(6, lineInfo.LinePosition); + Assert.AreEqual(7, lineInfo.LinePosition); Assert.AreEqual(true, lineInfo.HasLineInfo()); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.String); Assert.AreEqual(jsonReader.Value, "Intel"); Assert.AreEqual(2, lineInfo.LineNumber); - Assert.AreEqual(14, lineInfo.LinePosition); + Assert.AreEqual(15, lineInfo.LinePosition); Assert.AreEqual(true, lineInfo.HasLineInfo()); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); Assert.AreEqual(jsonReader.Value, "Drives"); Assert.AreEqual(3, lineInfo.LineNumber); - Assert.AreEqual(9, lineInfo.LinePosition); + Assert.AreEqual(10, lineInfo.LinePosition); Assert.AreEqual(true, lineInfo.HasLineInfo()); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.StartArray); Assert.AreEqual(3, lineInfo.LineNumber); - Assert.AreEqual(11, lineInfo.LinePosition); + Assert.AreEqual(12, lineInfo.LinePosition); Assert.AreEqual(true, lineInfo.HasLineInfo()); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.String); Assert.AreEqual(jsonReader.Value, "DVD read/writer"); Assert.AreEqual(4, lineInfo.LineNumber); - Assert.AreEqual(21, lineInfo.LinePosition); + Assert.AreEqual(22, lineInfo.LinePosition); Assert.AreEqual(true, lineInfo.HasLineInfo()); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.String); Assert.AreEqual(jsonReader.Value, "500 gigabyte hard drive"); Assert.AreEqual(5, lineInfo.LineNumber); - Assert.AreEqual(29, lineInfo.LinePosition); + Assert.AreEqual(30, lineInfo.LinePosition); Assert.AreEqual(true, lineInfo.HasLineInfo()); jsonReader.Read(); diff --git a/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs index a5ceea07..f578e513 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs @@ -741,14 +741,14 @@ namespace Newtonsoft.Json.Tests.Linq } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected state: Finished. Line 5, position 3.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Additional text encountered after finished reading JSON content: ,. Line 5, position 2.")] public void ParseAdditionalContent() { string json = @"[ ""Small"", ""Medium"", ""Large"" -], 987987"; +],"; JToken.Parse(json); } diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index e24a613c..91e51fdf 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -64,7 +64,7 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("4.0.3.0")] +[assembly: AssemblyVersion("4.0.5.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.0.3.14319")] +[assembly: AssemblyFileVersion("4.0.5.14516")] #endif diff --git a/Src/Newtonsoft.Json.Tests/Schema/ExtensionsTests.cs b/Src/Newtonsoft.Json.Tests/Schema/ExtensionsTests.cs index 2d02d979..1314733a 100644 --- a/Src/Newtonsoft.Json.Tests/Schema/ExtensionsTests.cs +++ b/Src/Newtonsoft.Json.Tests/Schema/ExtensionsTests.cs @@ -108,7 +108,7 @@ namespace Newtonsoft.Json.Tests.Schema List errors = new List(); o.Validate(schema, (sender, args) => errors.Add(args.Message)); - Assert.AreEqual("Invalid type. Expected String but got Integer. Line 1, position 9.", errors[0]); + Assert.AreEqual("Invalid type. Expected String but got Integer. Line 1, position 8.", errors[0]); Assert.AreEqual(1, errors.Count); } diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index fd394f26..14df3b61 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -3263,7 +3263,7 @@ keyword such as type of business."" Converters = new List {new StringAppenderConverter()} }); - Assert.AreEqual(p.Name, "Existing,Appended"); + Assert.AreEqual("Existing,Appended", p.Name); } [Test] @@ -5073,6 +5073,45 @@ keyword such as type of business."" } } #endif + + [Test] + public void ReadForTypeHackFixDecimal() + { + IList d1 = new List { 1.1m}; + + string json = JsonConvert.SerializeObject(d1); + + IList d2 = JsonConvert.DeserializeObject>(json); + + Assert.AreEqual(d1.Count, d2.Count); + Assert.AreEqual(d1[0], d2[0]); + } + + [Test] + public void ReadForTypeHackFixDateTimeOffset() + { + IList d1 = new List { null }; + + string json = JsonConvert.SerializeObject(d1); + + IList d2 = JsonConvert.DeserializeObject>(json); + + Assert.AreEqual(d1.Count, d2.Count); + Assert.AreEqual(d1[0], d2[0]); + } + + [Test] + public void ReadForTypeHackFixByteArray() + { + IList d1 = new List { null }; + + string json = JsonConvert.SerializeObject(d1); + + IList d2 = JsonConvert.DeserializeObject>(json); + + Assert.AreEqual(d1.Count, d2.Count); + Assert.AreEqual(d1[0], d2[0]); + } } public class DecimalTestClass diff --git a/Src/Newtonsoft.Json/Bson/BsonReader.cs b/Src/Newtonsoft.Json/Bson/BsonReader.cs index 8f3b2de6..8391e062 100644 --- a/Src/Newtonsoft.Json/Bson/BsonReader.cs +++ b/Src/Newtonsoft.Json/Bson/BsonReader.cs @@ -171,6 +171,9 @@ namespace Newtonsoft.Json.Bson if (TokenType == JsonToken.Bytes) return (byte[])Value; + if (ReaderIsSerializerInArray()) + return null; + throw new JsonReaderException("Error reading bytes. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } @@ -214,6 +217,9 @@ namespace Newtonsoft.Json.Bson return (decimal)Value; } + if (ReaderIsSerializerInArray()) + return null; + throw new JsonReaderException("Error reading decimal. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } @@ -235,7 +241,10 @@ namespace Newtonsoft.Json.Bson SetToken(JsonToken.Date, new DateTimeOffset((DateTime)Value)); return (DateTimeOffset)Value; } - + + if (ReaderIsSerializerInArray()) + return null; + throw new JsonReaderException("Error reading date. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } #endif diff --git a/Src/Newtonsoft.Json/JsonConvert.cs b/Src/Newtonsoft.Json/JsonConvert.cs index 88407c5f..4f9b89a6 100644 --- a/Src/Newtonsoft.Json/JsonConvert.cs +++ b/Src/Newtonsoft.Json/JsonConvert.cs @@ -619,7 +619,7 @@ namespace Newtonsoft.Json { JsonSerializer jsonSerializer = JsonSerializer.Create(settings); - StringBuilder sb = new StringBuilder(128); + StringBuilder sb = new StringBuilder(256); StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture); using (JsonTextWriter jsonWriter = new JsonTextWriter(sw)) { diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index d282a2a6..c2cd3089 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -40,7 +40,7 @@ namespace Newtonsoft.Json /// /// Specifies the state of the reader. /// - protected enum State + protected internal enum State { /// /// The Read method has not been called. @@ -99,10 +99,15 @@ namespace Newtonsoft.Json // current Token data private JsonToken _token; private object _value; - private Type _valueType; private char _quoteChar; - private State _currentState; + internal State _currentState; private JTokenType _currentTypeContext; + private bool _serializerInArray; + + internal void SetSerializeInArray(bool serializerInArray) + { + _serializerInArray = serializerInArray; + } /// /// Gets the current reader state. @@ -157,7 +162,7 @@ namespace Newtonsoft.Json /// public virtual Type ValueType { - get { return _valueType; } + get { return (_value != null) ? _value.GetType() : null; } } /// @@ -242,6 +247,9 @@ namespace Newtonsoft.Json /// public void Skip() { + if (TokenType == JsonToken.PropertyName) + Read(); + if (IsStartToken(TokenType)) { int depth = Depth; @@ -266,7 +274,7 @@ namespace Newtonsoft.Json /// /// The new token. /// The value. - protected virtual void SetToken(JsonToken newToken, object value) + protected void SetToken(JsonToken newToken, object value) { _token = newToken; @@ -286,19 +294,15 @@ namespace Newtonsoft.Json break; case JsonToken.EndObject: ValidateEnd(JsonToken.EndObject); - _currentState = State.PostValue; break; case JsonToken.EndArray: ValidateEnd(JsonToken.EndArray); - _currentState = State.PostValue; break; case JsonToken.EndConstructor: ValidateEnd(JsonToken.EndConstructor); - _currentState = State.PostValue; break; case JsonToken.PropertyName: _currentState = State.Property; - Push(JTokenType.Property); break; case JsonToken.Undefined: case JsonToken.Integer: @@ -309,24 +313,11 @@ namespace Newtonsoft.Json case JsonToken.String: case JsonToken.Raw: case JsonToken.Bytes: - _currentState = State.PostValue; + _currentState = (Peek() != JTokenType.None) ? State.PostValue : State.Finished; break; } - JTokenType current = Peek(); - if (current == JTokenType.Property && _currentState == State.PostValue) - Pop(); - - if (value != null) - { - _value = value; - _valueType = value.GetType(); - } - else - { - _value = null; - _valueType = null; - } + _value = value; } private void ValidateEnd(JsonToken endToken) @@ -335,6 +326,8 @@ namespace Newtonsoft.Json if (GetTypeForCloseToken(endToken) != currentObject) throw new JsonReaderException("JsonToken {0} is not valid for closing JsonType {1}.".FormatWith(CultureInfo.InvariantCulture, endToken, currentObject)); + + _currentState = (Peek() != JTokenType.None) ? State.PostValue : State.Finished; } /// @@ -388,8 +381,8 @@ namespace Newtonsoft.Json case JsonToken.StartObject: case JsonToken.StartArray: case JsonToken.StartConstructor: - case JsonToken.PropertyName: return true; + case JsonToken.PropertyName: case JsonToken.None: case JsonToken.Comment: case JsonToken.Integer: @@ -425,6 +418,11 @@ namespace Newtonsoft.Json } } + internal bool ReaderIsSerializerInArray() + { + return (TokenType == JsonToken.EndArray && _serializerInArray); + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -451,7 +449,6 @@ namespace Newtonsoft.Json _currentState = State.Closed; _token = JsonToken.None; _value = null; - _valueType = null; } } } diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 26cf0d8f..7564d9e3 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using System.IO; using System.Xml; @@ -48,15 +49,20 @@ namespace Newtonsoft.Json #endif } - private readonly TextReader _reader; - private readonly StringBuffer _buffer; - private char? _lastChar; - private int _currentLinePosition; - private int _currentLineNumber; - private bool _end; private ReadType _readType; private CultureInfo _culture; + private readonly TextReader _reader; + + private char[] _chars; + private int _charsUsed; + private int _charPos; + private int _lineStartPos; + private int _lineNumber; + private bool _isEndOfFile; + private StringBuffer _buffer; + private StringReference _stringReference; + /// /// Gets or sets the culture used when reading JSON. Defaults to . /// @@ -76,33 +82,60 @@ namespace Newtonsoft.Json throw new ArgumentNullException("reader"); _reader = reader; - _buffer = new StringBuffer(4096); - _currentLineNumber = 1; + _lineNumber = 1; + + _chars = new char[4097]; + } + + internal void SetCharBuffer(char[] chars) + { + _chars = chars; + } + + private StringBuffer GetBuffer() + { + if (_buffer == null) + { + _buffer = new StringBuffer(4096); + } + else + { + _buffer.Position = 0; + } + + return _buffer; + } + + private void OnNewLine(int pos) + { + _lineNumber++; + _lineStartPos = pos - 1; } private void ParseString(char quote) { + _charPos++; + + ShiftBufferIfNeeded(); ReadStringIntoBuffer(quote); if (_readType == ReadType.ReadAsBytes) { byte[] data; - if (_buffer.Position == 0) + if (_stringReference.Length == 0) { data = new byte[0]; } else { - data = Convert.FromBase64CharArray(_buffer.GetInternalBuffer(), 0, _buffer.Position); - _buffer.Position = 0; + data = Convert.FromBase64CharArray(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length); } SetToken(JsonToken.Bytes, data); } else { - string text = _buffer.ToString(); - _buffer.Position = 0; + string text = _stringReference.ToString(); if (text.StartsWith("/Date(", StringComparison.Ordinal) && text.EndsWith(")/", StringComparison.Ordinal)) { @@ -116,95 +149,124 @@ namespace Newtonsoft.Json } } - private void ReadStringIntoBuffer(char quote) + private static void BlockCopyChars(char[] src, int srcOffset, char[] dst, int dstOffset, int count) { - while (true) + const int charByteCount = 2; + + Buffer.BlockCopy(src, srcOffset * charByteCount, dst, dstOffset * charByteCount, count * charByteCount); + } + + private void ShiftBufferIfNeeded() + { + // once in the last 10% of the buffer shift the remainling 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) { - char currentChar = MoveNext(); + int count = _charsUsed - _charPos; + if (count > 0) + BlockCopyChars(_chars, _charPos, _chars, 0, count); - switch (currentChar) + _lineStartPos -= _charPos; + _charPos = 0; + _charsUsed = count; + _chars[_charsUsed] = '\0'; + } + } + + private int ReadData(bool append) + { + return ReadData(append, 0); + } + + private int ReadData(bool append, int charsRequired) + { + if (_isEndOfFile) + return 0; + + // char buffer is full + if (_charsUsed + charsRequired >= _chars.Length - 1) + { + if (append) { - case '\0': - if (_end) - throw CreateJsonReaderException("Unterminated string. Expected delimiter: {0}. Line {1}, position {2}.", quote, _currentLineNumber, _currentLinePosition); + // 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); - _buffer.Append('\0'); - break; - case '\\': - if ((currentChar = MoveNext()) != '\0' || !_end) - { - switch (currentChar) - { - case 'b': - _buffer.Append('\b'); - break; - case 't': - _buffer.Append('\t'); - break; - case 'n': - _buffer.Append('\n'); - break; - case 'f': - _buffer.Append('\f'); - break; - case 'r': - _buffer.Append('\r'); - break; - case '\\': - _buffer.Append('\\'); - break; - case '"': - case '\'': - case '/': - _buffer.Append(currentChar); - break; - case 'u': - char[] hexValues = new char[4]; - for (int i = 0; i < hexValues.Length; i++) - { - if ((currentChar = MoveNext()) != '\0' || !_end) - hexValues[i] = currentChar; - else - throw CreateJsonReaderException("Unexpected end while parsing unicode character. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); - } + // increase the size of the buffer + char[] dst = new char[newArrayLength]; - char hexChar = Convert.ToChar(int.Parse(new string(hexValues), NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo)); - _buffer.Append(hexChar); - break; - default: - throw CreateJsonReaderException("Bad JSON escape sequence: {0}. Line {1}, position {2}.", @"\" + currentChar, _currentLineNumber, _currentLinePosition); - } - } - else - { - throw CreateJsonReaderException("Unterminated string. Expected delimiter: {0}. Line {1}, position {2}.", quote, _currentLineNumber, _currentLinePosition); - } - break; - case '"': - case '\'': - if (currentChar == quote) - { - return; - } - else - { - _buffer.Append(currentChar); - } - break; - default: - _buffer.Append(currentChar); - break; + BlockCopyChars(_chars, 0, dst, 0, _chars.Length); + + _chars = dst; + } + else + { + int remainingCharCount = _charsUsed - _charPos; + + if (remainingCharCount + charsRequired + 1 >= _chars.Length) + { + // the remaining count plus the required is bigger than the current buffer size + char[] dst = new char[remainingCharCount + charsRequired + 1]; + + if (remainingCharCount > 0) + BlockCopyChars(_chars, _charPos, dst, 0, remainingCharCount); + + _chars = dst; + } + else + { + // copy any remaining data to the beginning of the buffer if needed and reset positions + if (remainingCharCount > 0) + BlockCopyChars(_chars, _charPos, _chars, 0, remainingCharCount); + } + + _lineStartPos -= _charPos; + _charPos = 0; + _charsUsed = remainingCharCount; } } + + int attemptCharReadCount = _chars.Length - _charsUsed - 1; + + int charsRead = _reader.Read(_chars, _charsUsed, attemptCharReadCount); + _charsUsed += charsRead; + + if (charsRead == 0) + _isEndOfFile = true; + + _chars[_charsUsed] = '\0'; + return charsRead; + } + + private bool EnsureChars(int relativePosition, bool append) + { + if (_charPos + relativePosition >= _charsUsed) + return ReadChars(relativePosition, append); + + return true; + } + + private bool ReadChars(int relativePosition, bool append) + { + if (_isEndOfFile) + return false; + + int charsRequired = _charPos + relativePosition - _charsUsed + 1; + + int charsRead = ReadData(append, charsRequired); + + if (charsRead < charsRequired) + return false; + return true; } private JsonReaderException CreateJsonReaderException(string format, params object[] args) { string message = format.FormatWith(CultureInfo.InvariantCulture, args); - return new JsonReaderException(message, null, _currentLineNumber, _currentLinePosition); + return new JsonReaderException(message, null, LineNumber, LinePosition); } - private TimeSpan ReadOffset(string offsetText) + private static TimeSpan ReadOffset(string offsetText) { bool negative = (offsetText[0] == '-'); @@ -270,53 +332,13 @@ namespace Newtonsoft.Json } } - private const int LineFeedValue = StringUtils.LineFeed; - private const int CarriageReturnValue = StringUtils.CarriageReturn; - - private char MoveNext() - { - int value = _reader.Read(); - - switch (value) - { - case -1: - _end = true; - return '\0'; - case CarriageReturnValue: - if (_reader.Peek() == LineFeedValue) - _reader.Read(); - - _currentLineNumber++; - _currentLinePosition = 0; - break; - case LineFeedValue: - _currentLineNumber++; - _currentLinePosition = 0; - break; - default: - _currentLinePosition++; - break; - } - - return (char) value; - } - - private bool HasNext() - { - return (_reader.Peek() != -1); - } - - private int PeekNext() - { - return _reader.Peek(); - } - /// /// 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. /// + [DebuggerStepThrough] public override bool Read() { _readType = ReadType.Read; @@ -329,10 +351,12 @@ namespace Newtonsoft.Json if (TokenType == JsonToken.StartObject) { - int startObjectLineNumber = _currentLineNumber; - int startObjectLinePosition = _currentLinePosition; + int startObjectLineNumber = LineNumber; + int startObjectLinePosition = LinePosition; + + if (!ReadInternal()) + throw CreateJsonReaderException("Unexpected end when reading bytes: Line {0}, position {1}.", LineNumber, LinePosition); - ReadInternal(); if (Value.ToString() == "$type") { ReadInternal(); @@ -365,7 +389,7 @@ namespace Newtonsoft.Json do { if (!ReadInternal()) - throw CreateJsonReaderException("Unexpected end when reading bytes: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected end when reading bytes: Line {0}, position {1}.", LineNumber, LinePosition); } while (TokenType == JsonToken.Comment); if (IsWrappedInTypeObject()) @@ -399,15 +423,17 @@ namespace Newtonsoft.Json // skip break; default: - throw CreateJsonReaderException("Unexpected token when reading bytes: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected token when reading bytes: {0}. Line {1}, position {2}.", TokenType, LineNumber, LinePosition); } } - throw CreateJsonReaderException("Unexpected end when reading bytes: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected end when reading bytes: Line {0}, position {1}.", LineNumber, LinePosition); } + if (ReaderIsSerializerInArray()) + return null; - throw CreateJsonReaderException("Unexpected token when reading bytes: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected token when reading bytes: {0}. Line {1}, position {2}.", TokenType, LineNumber, LinePosition); } /// @@ -421,7 +447,7 @@ namespace Newtonsoft.Json do { if (!ReadInternal()) - throw CreateJsonReaderException("Unexpected end when reading decimal: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected end when reading decimal: Line {0}, position {1}.", LineNumber, LinePosition); } while (TokenType == JsonToken.Comment); if (TokenType == JsonToken.Null) @@ -436,7 +462,10 @@ namespace Newtonsoft.Json return d; } - throw CreateJsonReaderException("Unexpected token when reading decimal: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition); + if (ReaderIsSerializerInArray()) + return null; + + throw CreateJsonReaderException("Unexpected token when reading decimal: {0}. Line {1}, position {2}.", TokenType, LineNumber, LinePosition); } #if !NET20 @@ -451,7 +480,7 @@ namespace Newtonsoft.Json do { if (!ReadInternal()) - throw CreateJsonReaderException("Unexpected end when reading date: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected end when reading date: Line {0}, position {1}.", LineNumber, LinePosition); } while (TokenType == JsonToken.Comment); if (TokenType == JsonToken.Null) @@ -466,7 +495,10 @@ namespace Newtonsoft.Json return dt; } - throw CreateJsonReaderException("Unexpected token when reading date: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition); + if (ReaderIsSerializerInArray()) + return null; + + throw CreateJsonReaderException("Unexpected token when reading date: {0}. Line {1}, position {2}.", TokenType, LineNumber, LinePosition); } #endif @@ -474,21 +506,13 @@ namespace Newtonsoft.Json { while (true) { - char currentChar; - if (_lastChar != null) + if (_charPos >= _charsUsed) { - currentChar = _lastChar.Value; - _lastChar = null; - } - else - { - currentChar = MoveNext(); + if (!ReadChars(0, false)) + return false; } - if (currentChar == '\0' && _end) - return false; - - switch (CurrentState) + switch (_currentState) { case State.Start: case State.Property: @@ -496,141 +520,353 @@ namespace Newtonsoft.Json case State.ArrayStart: case State.Constructor: case State.ConstructorStart: - return ParseValue(currentChar); + return ParseValue(); case State.Complete: break; case State.Object: case State.ObjectStart: - return ParseObject(currentChar); + return ParseObject(); case State.PostValue: // returns true if it hits // end of object or array - if (ParsePostValue(currentChar)) + if (ParsePostValue()) return true; break; + case State.Finished: + if (EnsureChars(0, false)) + { + EatWhitespace(false); + if (_isEndOfFile) + { + return false; + } + if (_chars[_charPos] == '/') + { + ParseComment(); + return true; + } + else + { + throw CreateJsonReaderException("Additional text encountered after finished reading JSON content: {0}. Line {1}, position {2}.", _chars[_charPos], LineNumber, LinePosition); + } + } + return false; case State.Closed: break; case State.Error: break; default: - throw CreateJsonReaderException("Unexpected state: {0}. Line {1}, position {2}.", CurrentState, _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected state: {0}. Line {1}, position {2}.", CurrentState, LineNumber, LinePosition); } } } - private bool ParsePostValue(char currentChar) + private void ReadStringIntoBuffer(char quote) { - do + int charPos = _charPos; + int initialPosition = _charPos; + int lastWritePosition = _charPos; + StringBuffer buffer = null; + + while (true) { + switch (_chars[charPos++]) + { + case '\0': + if (_charsUsed == charPos - 1) + { + charPos--; + + if (ReadData(true) == 0) + { + _charPos = charPos; + throw CreateJsonReaderException("Unterminated string. Expected delimiter: {0}. Line {1}, position {2}.", quote, LineNumber, LinePosition); + } + } + break; + case '\\': + _charPos = charPos; + if (!EnsureChars(0, true)) + { + _charPos = charPos; + throw CreateJsonReaderException("Unterminated string. Expected delimiter: {0}. Line {1}, position {2}.", quote, LineNumber, LinePosition); + } + + // start of escape sequence + int escapeStartPos = charPos - 1; + + char currentChar = _chars[charPos]; + + char writeChar; + + switch (currentChar) + { + case 'b': + charPos++; + writeChar = '\b'; + break; + case 't': + charPos++; + writeChar = '\t'; + break; + case 'n': + charPos++; + writeChar = '\n'; + break; + case 'f': + charPos++; + writeChar = '\f'; + break; + case 'r': + charPos++; + writeChar = '\r'; + break; + case '\\': + charPos++; + writeChar = '\\'; + break; + case '"': + case '\'': + case '/': + writeChar = currentChar; + charPos++; + break; + case 'u': + charPos++; + _charPos = charPos; + if (EnsureChars(4, true)) + { + string hexValues = new string(_chars, charPos, 4); + char hexChar = Convert.ToChar(int.Parse(hexValues, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo)); + writeChar = hexChar; + + charPos += 4; + } + else + { + _charPos = charPos; + throw CreateJsonReaderException("Unexpected end while parsing unicode character. Line {0}, position {1}.", LineNumber, LinePosition); + } + break; + default: + charPos++; + _charPos = charPos; + throw CreateJsonReaderException("Bad JSON escape sequence: {0}. Line {1}, position {2}.", @"\" + currentChar, LineNumber, LinePosition); + } + + if (buffer == null) + buffer = GetBuffer(); + + if (escapeStartPos > lastWritePosition) + { + buffer.Append(_chars, lastWritePosition, escapeStartPos - lastWritePosition); + } + + buffer.Append(writeChar); + + lastWritePosition = charPos; + break; + case StringUtils.CarriageReturn: + _charPos = charPos - 1; + ProcessCarriageReturn(true); + charPos = _charPos; + break; + case StringUtils.LineFeed: + _charPos = charPos - 1; + ProcessLineFeed(); + charPos = _charPos; + break; + case '"': + case '\'': + if (_chars[charPos - 1] == quote) + { + charPos--; + + if (initialPosition == lastWritePosition) + { + _stringReference = new StringReference(_chars, initialPosition, charPos - initialPosition); + } + else + { + if (buffer == null) + buffer = GetBuffer(); + + if (charPos > lastWritePosition) + buffer.Append(_chars, lastWritePosition, charPos - lastWritePosition); + + _stringReference = new StringReference(buffer.GetInternalBuffer(), 0, buffer.Position); + } + + charPos++; + _charPos = charPos; + return; + } + break; + } + } + } + + private void ClearRecentString() + { + if (_buffer != null) + _buffer.Position = 0; + + _stringReference = new StringReference(); + } + + private bool ParsePostValue() + { + while (true) + { + char currentChar = _chars[_charPos]; + switch (currentChar) { + case '\0': + if (_charsUsed == _charPos) + { + if (ReadData(false) == 0) + return false; + } + else + { + _charPos++; + } + break; case '}': + _charPos++; SetToken(JsonToken.EndObject); return true; case ']': + _charPos++; SetToken(JsonToken.EndArray); return true; case ')': + _charPos++; SetToken(JsonToken.EndConstructor); return true; case '/': ParseComment(); return true; case ',': + _charPos++; + // finished parsing SetStateBasedOnCurrent(); return false; case ' ': case StringUtils.Tab: - case StringUtils.LineFeed: - case StringUtils.CarriageReturn: // eat + _charPos++; + break; + case StringUtils.CarriageReturn: + ProcessCarriageReturn(false); + break; + case StringUtils.LineFeed: + ProcessLineFeed(); break; default: if (char.IsWhiteSpace(currentChar)) { // eat + _charPos++; } else { - throw CreateJsonReaderException("After parsing a value an unexpected character was encountered: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("After parsing a value an unexpected character was encountered: {0}. Line {1}, position {2}.", currentChar, LineNumber, LinePosition); } break; } - } while ((currentChar = MoveNext()) != '\0' || !_end); - - return false; + } } - private bool ParseObject(char currentChar) + private bool ParseObject() { - do + while (true) { + char currentChar = _chars[_charPos]; + switch (currentChar) { + case '\0': + if (_charsUsed == _charPos) + { + if (ReadData(false) == 0) + return false; + } + else + { + _charPos++; + } + break; case '}': SetToken(JsonToken.EndObject); + _charPos++; return true; case '/': ParseComment(); return true; + case StringUtils.CarriageReturn: + ProcessCarriageReturn(false); + break; + case StringUtils.LineFeed: + ProcessLineFeed(); + break; case ' ': case StringUtils.Tab: - case StringUtils.LineFeed: - case StringUtils.CarriageReturn: // eat + _charPos++; break; default: if (char.IsWhiteSpace(currentChar)) { // eat + _charPos++; } else { - return ParseProperty(currentChar); + return ParseProperty(); } break; } - } while ((currentChar = MoveNext()) != '\0' || !_end); - - return false; + } } - private bool ParseProperty(char firstChar) + private bool ParseProperty() { - char currentChar = firstChar; + char firstChar = _chars[_charPos]; char quoteChar; - if (ValidIdentifierChar(currentChar)) + if (firstChar == '"' || firstChar == '\'') + { + _charPos++; + quoteChar = firstChar; + ShiftBufferIfNeeded(); + ReadStringIntoBuffer(quoteChar); + } + else if (ValidIdentifierChar(firstChar)) { quoteChar = '\0'; - currentChar = ParseUnquotedProperty(currentChar); - } - else if (currentChar == '"' || currentChar == '\'') - { - quoteChar = currentChar; - ReadStringIntoBuffer(quoteChar); - currentChar = MoveNext(); + ShiftBufferIfNeeded(); + ParseUnquotedProperty(); } else { - throw CreateJsonReaderException("Invalid property identifier character: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Invalid property identifier character: {0}. Line {1}, position {2}.", _chars[_charPos], LineNumber, LinePosition); } - if (currentChar != ':') - { - currentChar = MoveNext(); + string propertyName = _stringReference.ToString(); - // finished property. skip any whitespace and move to colon - EatWhitespace(currentChar, false, out currentChar); + EatWhitespace(false); - if (currentChar != ':') - throw CreateJsonReaderException("Invalid character after parsing property name. Expected ':' but got: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition); - } + if (_chars[_charPos] != ':') + throw CreateJsonReaderException("Invalid character after parsing property name. Expected ':' but got: {0}. Line {1}, position {2}.", _chars[_charPos], LineNumber, LinePosition); - SetToken(JsonToken.PropertyName, _buffer.ToString()); + _charPos++; + + SetToken(JsonToken.PropertyName, propertyName); QuoteChar = quoteChar; - _buffer.Position = 0; + ClearRecentString(); return true; } @@ -640,38 +876,64 @@ namespace Newtonsoft.Json return (char.IsLetterOrDigit(value) || value == '_' || value == '$'); } - private char ParseUnquotedProperty(char firstChar) + private void ParseUnquotedProperty() { + int initialPosition = _charPos; + // parse unquoted property name until whitespace or colon - _buffer.Append(firstChar); - - char currentChar; - - while ((currentChar = MoveNext()) != '\0' || !_end) + while (true) { - if (char.IsWhiteSpace(currentChar) || currentChar == ':') + switch (_chars[_charPos]) { - return currentChar; - } - else if (ValidIdentifierChar(currentChar)) - { - _buffer.Append(currentChar); - } - else - { - throw CreateJsonReaderException("Invalid JavaScript property identifier character: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition); + case '\0': + if (_charsUsed == _charPos) + { + if (ReadData(true) == 0) + throw CreateJsonReaderException("Unexpected end when parsing unquoted property name. Line {0}, position {1}.", LineNumber, LinePosition); + + break; + } + + _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition); + return; + default: + char currentChar = _chars[_charPos]; + + if (ValidIdentifierChar(currentChar)) + { + _charPos++; + break; + } + else if (char.IsWhiteSpace(currentChar) || currentChar == ':') + { + _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition); + return; + } + + throw CreateJsonReaderException("Invalid JavaScript property identifier character: {0}. Line {1}, position {2}.", currentChar, LineNumber, LinePosition); } } - - throw CreateJsonReaderException("Unexpected end when parsing unquoted property name. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); } - private bool ParseValue(char currentChar) + private bool ParseValue() { - do + while (true) { + char currentChar = _chars[_charPos]; + switch (currentChar) { + case '\0': + if (_charsUsed == _charPos) + { + if (ReadData(false) == 0) + return false; + } + else + { + _charPos++; + } + break; case '"': case '\'': ParseString(currentChar); @@ -683,20 +945,20 @@ namespace Newtonsoft.Json ParseFalse(); return true; case 'n': - if (HasNext()) + if (EnsureChars(1, true)) { - char next = (char) PeekNext(); + char next = _chars[_charPos + 1]; if (next == 'u') ParseNull(); else if (next == 'e') ParseConstructor(); else - throw CreateJsonReaderException("Unexpected character encountered while parsing value: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected character encountered while parsing value: {0}. Line {1}, position {2}.", _chars[_charPos], LineNumber, LinePosition); } else { - throw CreateJsonReaderException("Unexpected end. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected end. Line {0}, position {1}.", LineNumber, LinePosition); } return true; case 'N': @@ -706,10 +968,10 @@ namespace Newtonsoft.Json ParseNumberPositiveInfinity(); return true; case '-': - if (PeekNext() == 'I') + if (EnsureChars(1, true) && _chars[_charPos + 1] == 'I') ParseNumberNegativeInfinity(); else - ParseNumber(currentChar); + ParseNumber(); return true; case '/': ParseComment(); @@ -718,118 +980,233 @@ namespace Newtonsoft.Json ParseUndefined(); return true; case '{': + _charPos++; SetToken(JsonToken.StartObject); return true; case '[': + _charPos++; SetToken(JsonToken.StartArray); return true; - case '}': - SetToken(JsonToken.EndObject); - return true; case ']': + _charPos++; SetToken(JsonToken.EndArray); return true; case ',': + // don't increment position, the next call to read will handle comma + // this is done to handle multiple empty comma values SetToken(JsonToken.Undefined); return true; case ')': + _charPos++; SetToken(JsonToken.EndConstructor); return true; + case StringUtils.CarriageReturn: + ProcessCarriageReturn(false); + break; + case StringUtils.LineFeed: + ProcessLineFeed(); + break; case ' ': case StringUtils.Tab: - case StringUtils.LineFeed: - case StringUtils.CarriageReturn: // eat + _charPos++; break; default: if (char.IsWhiteSpace(currentChar)) { // eat + _charPos++; + break; } else if (char.IsNumber(currentChar) || currentChar == '-' || currentChar == '.') { - ParseNumber(currentChar); + ParseNumber(); return true; } else { - throw CreateJsonReaderException("Unexpected character encountered while parsing value: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Unexpected character encountered while parsing value: {0}. Line {1}, position {2}.", currentChar, LineNumber, LinePosition); + } + } + } + } + + private void ProcessLineFeed() + { + _charPos++; + OnNewLine(_charPos); + } + + private void ProcessCarriageReturn(bool append) + { + _charPos++; + + if (EnsureChars(1, append) && _chars[_charPos] == StringUtils.LineFeed) + _charPos++; + + OnNewLine(_charPos); + } + + private bool EatWhitespace(bool oneOrMore) + { + bool finished = false; + bool ateWhitespace = false; + while (!finished) + { + char currentChar = _chars[_charPos]; + + switch (currentChar) + { + case '\0': + if (_charsUsed == _charPos) + { + if (ReadData(false) == 0) + finished = true; + } + else + { + _charPos++; + } + break; + case StringUtils.CarriageReturn: + ProcessCarriageReturn(false); + break; + case StringUtils.LineFeed: + ProcessLineFeed(); + break; + default: + if (currentChar == ' ' || char.IsWhiteSpace(currentChar)) + { + ateWhitespace = true; + _charPos++; + } + else + { + finished = true; } break; } - } while ((currentChar = MoveNext()) != '\0' || !_end); - - return false; - } - - private bool EatWhitespace(char initialChar, bool oneOrMore, out char finalChar) - { - bool whitespace = false; - char currentChar = initialChar; - while (currentChar == ' ' || char.IsWhiteSpace(currentChar)) - { - whitespace = true; - currentChar = MoveNext(); } - finalChar = currentChar; - - return (!oneOrMore || whitespace); + return (!oneOrMore || ateWhitespace); } private void ParseConstructor() { - if (MatchValue('n', "new", true)) + if (MatchValueWithTrailingSeperator("new")) { - char currentChar = MoveNext(); + EatWhitespace(false); - if (EatWhitespace(currentChar, true, out currentChar)) + int initialPosition = _charPos; + int endPosition; + + while (true) { - while (char.IsLetter(currentChar)) + char currentChar = _chars[_charPos]; + if (currentChar == '\0') { - _buffer.Append(currentChar); - currentChar = MoveNext(); + if (_charsUsed == _charPos) + { + if (ReadData(true) == 0) + throw CreateJsonReaderException("Unexpected end while parsing constructor."); + } + else + { + endPosition = _charPos; + _charPos++; + break; + } + } + else if (char.IsLetterOrDigit(currentChar)) + { + _charPos++; + } + else if (currentChar == StringUtils.CarriageReturn) + { + endPosition = _charPos; + ProcessCarriageReturn(true); + break; + } + else if (currentChar == StringUtils.LineFeed) + { + endPosition = _charPos; + ProcessLineFeed(); + break; + } + else if (char.IsWhiteSpace(currentChar)) + { + endPosition = _charPos; + _charPos++; + break; + } + else if (currentChar == '(') + { + endPosition = _charPos; + break; + } + else + { + throw CreateJsonReaderException("Unexpected character while parsing constructor: {0}. Line {1}, position {2}.", currentChar, LineNumber, LinePosition); } - - EatWhitespace(currentChar, false, out currentChar); - - if (currentChar != '(') - throw CreateJsonReaderException("Unexpected character while parsing constructor: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition); - - string constructorName = _buffer.ToString(); - _buffer.Position = 0; - - SetToken(JsonToken.StartConstructor, constructorName); } + + _stringReference = new StringReference(_chars, initialPosition, endPosition - initialPosition); + string constructorName = _stringReference.ToString(); + + EatWhitespace(false); + + if (_chars[_charPos] != '(') + throw CreateJsonReaderException("Unexpected character while parsing constructor: {0}. Line {1}, position {2}.", _chars[_charPos], LineNumber, LinePosition); + + _charPos++; + + ClearRecentString(); + + SetToken(JsonToken.StartConstructor, constructorName); } } - private void ParseNumber(char firstChar) + private void ParseNumber() { - char currentChar = firstChar; + ShiftBufferIfNeeded(); + + char firstChar = _chars[_charPos]; + int initialPosition = _charPos; // parse until seperator character or end - bool end = false; - do + while (true) { - if (IsSeperator(currentChar)) + char currentChar = _chars[_charPos]; + + if (currentChar == '\0') { - end = true; - _lastChar = currentChar; + if (_charsUsed == _charPos) + { + if (ReadData(true) == 0) + break; + } + else + { + _charPos++; + } + } + else if (IsSeperator(currentChar)) + { + break; } else { - _buffer.Append(currentChar); + _charPos++; } + } - } while (!end && ((currentChar = MoveNext()) != '\0' || !_end)); + _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition); - string number = _buffer.ToString(); object numberValue; JsonToken numberType; - bool singleDigit = (char.IsDigit(firstChar) && number.Length == 1); - bool nonBase10 = (firstChar == '0' && number.Length > 1 && number[1] != '.'); + bool singleDigit = (char.IsDigit(firstChar) && _stringReference.Length == 1); + bool nonBase10 = (firstChar == '0' && _stringReference.Length > 1 && _stringReference.Chars[_stringReference.StartIndex + 1] != '.'); if (_readType == ReadType.ReadAsDecimal) { @@ -840,6 +1217,8 @@ namespace Newtonsoft.Json } else if (nonBase10) { + string number = _stringReference.ToString(); + // decimal.Parse doesn't support parsing hexadecimal values long integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) @@ -849,6 +1228,8 @@ namespace Newtonsoft.Json } else { + string number = _stringReference.ToString(); + numberValue = decimal.Parse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture); } @@ -859,38 +1240,45 @@ 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) { + string number = _stringReference.ToString(); + numberValue = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); numberType = JsonToken.Integer; - } - // it's faster to do 3 indexof with single characters than an indexofany - else if (number.IndexOf('.') != -1 || number.IndexOf('E') != -1 || number.IndexOf('e') != -1) - { - numberValue = Convert.ToDouble(number, CultureInfo.InvariantCulture); - numberType = JsonToken.Float; } else { - try - { - numberValue = Convert.ToInt64(number, CultureInfo.InvariantCulture); - } - catch (OverflowException ex) - { - throw new JsonReaderException("JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, number), ex); - } + string number = _stringReference.ToString(); - numberType = JsonToken.Integer; + // it's faster to do 3 indexof with single characters than an indexofany + if (number.IndexOf('.') != -1 || number.IndexOf('E') != -1 || number.IndexOf('e') != -1) + { + numberValue = Convert.ToDouble(number, CultureInfo.InvariantCulture); + numberType = JsonToken.Float; + } + else + { + try + { + numberValue = Convert.ToInt64(number, CultureInfo.InvariantCulture); + } + catch (OverflowException ex) + { + throw new JsonReaderException("JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, number), ex); + } + + numberType = JsonToken.Integer; + } } } - _buffer.Position = 0; + ClearRecentString(); SetToken(numberType, numberValue); } @@ -898,78 +1286,93 @@ namespace Newtonsoft.Json private void ParseComment() { // should have already parsed / character before reaching this method + _charPos++; - char currentChar = MoveNext(); + if (!EnsureChars(1, false) || _chars[_charPos] != '*') + throw CreateJsonReaderException("Error parsing comment. Expected: *, got {0}. Line {1}, position {2}.", _chars[_charPos], LineNumber, LinePosition); + else + _charPos++; - if (currentChar == '*') + int initialPosition = _charPos; + + bool commentFinished = false; + + while (!commentFinished) { - while ((currentChar = MoveNext()) != '\0' || !_end) + switch (_chars[_charPos]) { - if (currentChar == '*') - { - if ((currentChar = MoveNext()) != '\0' || !_end) + case '\0': + if (_charsUsed == _charPos) { - if (currentChar == '/') + if (ReadData(true) == 0) + throw CreateJsonReaderException("Unexpected end while parsing comment."); + } + else + { + _charPos++; + } + break; + case '*': + _charPos++; + + if (EnsureChars(0, true)) + { + if (_chars[_charPos] == '/') { - break; - } - else - { - _buffer.Append('*'); - _buffer.Append(currentChar); + _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition - 1); + + _charPos++; + commentFinished = true; } } - } - else - { - _buffer.Append(currentChar); - } + break; + case StringUtils.CarriageReturn: + ProcessCarriageReturn(true); + break; + case StringUtils.LineFeed: + ProcessLineFeed(); + break; + default: + _charPos++; + break; } } - else - { - throw CreateJsonReaderException("Error parsing comment. Expected: *. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); - } - SetToken(JsonToken.Comment, _buffer.ToString()); + SetToken(JsonToken.Comment, _stringReference.ToString()); - _buffer.Position = 0; + ClearRecentString(); } - private bool MatchValue(char firstChar, string value) + private bool MatchValue(string value) { - char currentChar = firstChar; + if (!EnsureChars(value.Length - 1, true)) + return false; - int i = 0; - do + for (int i = 0; i < value.Length; i++) { - if (currentChar != value[i]) + if (_chars[_charPos + i] != value[i]) { - break; + return false; } - i++; - } while (i < value.Length && ((currentChar = MoveNext()) != '\0' || !_end)); + } - return (i == value.Length); + _charPos += value.Length; + + return true; } - private bool MatchValue(char firstChar, string value, bool noTrailingNonSeperatorCharacters) + private bool MatchValueWithTrailingSeperator(string value) { // will match value and then move to the next character, checking that it is a seperator character - bool match = MatchValue(firstChar, value); + bool match = MatchValue(value); - if (!noTrailingNonSeperatorCharacters) - { - return match; - } - else - { - int c = PeekNext(); - char next = (c != -1) ? (char) c : '\0'; - bool matchAndNoTrainingNonSeperatorCharacters = (match && (next == '\0' || IsSeperator(next))); + if (!match) + return false; - return matchAndNoTrainingNonSeperatorCharacters; - } + if (!EnsureChars(0, false)) + return true; + + return IsSeperator(_chars[_charPos]) || _chars[_charPos] == '\0'; } private bool IsSeperator(char c) @@ -982,7 +1385,10 @@ namespace Newtonsoft.Json return true; case '/': // check next character to see if start of a comment - return (HasNext() && PeekNext() == '*'); + if (!EnsureChars(1, false)) + return false; + + return (_chars[_charPos + 1] == '*'); case ')': if (CurrentState == State.Constructor || CurrentState == State.ConstructorStart) return true; @@ -1006,85 +1412,85 @@ namespace Newtonsoft.Json // check characters equal 'true' // and that it is followed by either a seperator character // or the text ends - if (MatchValue('t', JsonConvert.True, true)) + if (MatchValueWithTrailingSeperator(JsonConvert.True)) { SetToken(JsonToken.Boolean, true); } else { - throw CreateJsonReaderException("Error parsing boolean value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Error parsing boolean value. Line {0}, position {1}.", LineNumber, LinePosition); } } private void ParseNull() { - if (MatchValue('n', JsonConvert.Null, true)) + if (MatchValueWithTrailingSeperator(JsonConvert.Null)) { SetToken(JsonToken.Null); } else { - throw CreateJsonReaderException("Error parsing null value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Error parsing null value. Line {0}, position {1}.", LineNumber, LinePosition); } } private void ParseUndefined() { - if (MatchValue('u', JsonConvert.Undefined, true)) + if (MatchValueWithTrailingSeperator(JsonConvert.Undefined)) { SetToken(JsonToken.Undefined); } else { - throw CreateJsonReaderException("Error parsing undefined value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Error parsing undefined value. Line {0}, position {1}.", LineNumber, LinePosition); } } private void ParseFalse() { - if (MatchValue('f', JsonConvert.False, true)) + if (MatchValueWithTrailingSeperator(JsonConvert.False)) { SetToken(JsonToken.Boolean, false); } else { - throw CreateJsonReaderException("Error parsing boolean value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Error parsing boolean value. Line {0}, position {1}.", LineNumber, LinePosition); } } private void ParseNumberNegativeInfinity() { - if (MatchValue('-', JsonConvert.NegativeInfinity, true)) + if (MatchValueWithTrailingSeperator(JsonConvert.NegativeInfinity)) { SetToken(JsonToken.Float, double.NegativeInfinity); } else { - throw CreateJsonReaderException("Error parsing negative infinity value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Error parsing negative infinity value. Line {0}, position {1}.", LineNumber, LinePosition); } } private void ParseNumberPositiveInfinity() { - if (MatchValue('I', JsonConvert.PositiveInfinity, true)) + if (MatchValueWithTrailingSeperator(JsonConvert.PositiveInfinity)) { SetToken(JsonToken.Float, double.PositiveInfinity); } else { - throw CreateJsonReaderException("Error parsing positive infinity value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Error parsing positive infinity value. Line {0}, position {1}.", LineNumber, LinePosition); } } private void ParseNumberNaN() { - if (MatchValue('N', JsonConvert.NaN, true)) + if (MatchValueWithTrailingSeperator(JsonConvert.NaN)) { SetToken(JsonToken.Float, double.NaN); } else { - throw CreateJsonReaderException("Error parsing NaN value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + throw CreateJsonReaderException("Error parsing NaN value. Line {0}, position {1}.", LineNumber, LinePosition); } } @@ -1123,10 +1529,10 @@ namespace Newtonsoft.Json { get { - if (CurrentState == State.Start) + if (CurrentState == State.Start && LinePosition == 0) return 0; - - return _currentLineNumber; + + return _lineNumber; } } @@ -1138,7 +1544,7 @@ namespace Newtonsoft.Json /// public int LinePosition { - get { return _currentLinePosition; } + get { return _charPos - _lineStartPos; } } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonValidatingReader.cs b/Src/Newtonsoft.Json/JsonValidatingReader.cs index 83d7a1b3..785de5b7 100644 --- a/Src/Newtonsoft.Json/JsonValidatingReader.cs +++ b/Src/Newtonsoft.Json/JsonValidatingReader.cs @@ -131,7 +131,7 @@ namespace Newtonsoft.Json } /// - /// Gets The Common Language Runtime (CLR) type for the current Json token. + /// Gets the Common Language Runtime (CLR) type for the current Json token. /// /// public override Type ValueType diff --git a/Src/Newtonsoft.Json/Linq/JToken.cs b/Src/Newtonsoft.Json/Linq/JToken.cs index 4b109832..0ca8f02c 100644 --- a/Src/Newtonsoft.Json/Linq/JToken.cs +++ b/Src/Newtonsoft.Json/Linq/JToken.cs @@ -1212,7 +1212,6 @@ namespace Newtonsoft.Json.Linq if (reader.TokenType == JsonToken.StartConstructor) return JConstructor.Load(reader); - // hack. change to look at TokenType rather than using value if (!JsonReader.IsStartToken(reader.TokenType)) return new JValue(reader.Value); diff --git a/Src/Newtonsoft.Json/Linq/JTokenReader.cs b/Src/Newtonsoft.Json/Linq/JTokenReader.cs index 8dc833ae..807f044d 100644 --- a/Src/Newtonsoft.Json/Linq/JTokenReader.cs +++ b/Src/Newtonsoft.Json/Linq/JTokenReader.cs @@ -59,6 +59,9 @@ namespace Newtonsoft.Json.Linq if (TokenType == JsonToken.Bytes) return (byte[])Value; + if (ReaderIsSerializerInArray()) + return null; + throw new JsonReaderException("Error reading bytes. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } @@ -102,6 +105,9 @@ namespace Newtonsoft.Json.Linq return (decimal) Value; } + if (ReaderIsSerializerInArray()) + return null; + throw new JsonReaderException("Error reading decimal. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } @@ -122,6 +128,9 @@ namespace Newtonsoft.Json.Linq return (DateTimeOffset)Value; } + if (ReaderIsSerializerInArray()) + return null; + throw new JsonReaderException("Error reading date. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } #endif diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index d627e511..6b4275f3 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -195,6 +195,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj index bf9867eb..4f79b12f 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj @@ -210,6 +210,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj index 68c0ffa6..8dca56ec 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj @@ -211,6 +211,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj index 0a4c9f73..93b4b1c1 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj @@ -181,6 +181,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 098ce151..980a105a 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -209,6 +209,7 @@ + diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 28b5ac30..a6e80ad8 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -83,9 +83,9 @@ using System.Security; // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("4.0.3.0")] +[assembly: AssemblyVersion("4.0.5.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.0.3.14319")] +[assembly: AssemblyFileVersion("4.0.5.14516")] #endif [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs index 30d35bda..21266c14 100644 --- a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs +++ b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs @@ -291,6 +291,14 @@ namespace Newtonsoft.Json.Schema CurrentSchema.Items.Add(GenerateInternal(collectionItemType, (!allowNullItem) ? Required.Always : Required.Default, false)); } } + else if (contract is JsonStringContract) + { + JsonSchemaType schemaType = (!ReflectionUtils.IsNullable(contract.UnderlyingType)) + ? JsonSchemaType.String + : AddNullType(JsonSchemaType.String, valueRequired); + + CurrentSchema.Type = schemaType; + } else if (contract is JsonPrimitiveContract) { CurrentSchema.Type = GetJsonSchemaType(type, valueRequired); @@ -324,14 +332,6 @@ namespace Newtonsoft.Json.Schema GenerateISerializableContract(type, (JsonISerializableContract) contract); } #endif - else if (contract is JsonStringContract) - { - JsonSchemaType schemaType = (!ReflectionUtils.IsNullable(contract.UnderlyingType)) - ? JsonSchemaType.String - : AddNullType(JsonSchemaType.String, valueRequired); - - CurrentSchema.Type = schemaType; - } else if (contract is JsonLinqContract) { CurrentSchema.Type = JsonSchemaType.Any; diff --git a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs index 34266d4a..2a1fd262 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs @@ -38,6 +38,7 @@ namespace Newtonsoft.Json.Serialization public class JsonArrayContract : JsonContract { internal Type CollectionItemType { get; private set; } + internal JsonContract CollectionItemContract { get; set; } private readonly bool _isCollectionItemTypeNullableType; private readonly Type _genericCollectionDefinitionType; diff --git a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs index c69fa858..3ad7a2f9 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs @@ -45,6 +45,8 @@ namespace Newtonsoft.Json.Serialization internal Type DictionaryKeyType { get; private set; } internal Type DictionaryValueType { get; private set; } + internal JsonContract DictionaryValueContract { get; set; } + private readonly bool _isDictionaryValueTypeNullableType; private readonly Type _genericCollectionDefinitionType; private Type _genericWrapperType; diff --git a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs index 322eb5cf..e0d055b7 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs @@ -32,6 +32,9 @@ namespace Newtonsoft.Json.Serialization /// public class JsonProperty { + // use to cache contract during deserialization + internal JsonContract PropertyContract { get; set; } + /// /// Gets or sets the name of the property. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 14cc050e..a393f799 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -102,23 +102,19 @@ namespace Newtonsoft.Json.Serialization return Serializer.ContractResolver.ResolveContract(type); } - private JsonContract GetContractSafe(Type type, object value) - { - if (value == null) - return GetContractSafe(type); - - return Serializer.ContractResolver.ResolveContract(value.GetType()); - } - public object Deserialize(JsonReader reader, Type objectType) { if (reader == null) throw new ArgumentNullException("reader"); - if (reader.TokenType == JsonToken.None && !ReadForType(reader, objectType, null)) + JsonContract contract = GetContractSafe(objectType); + + JsonConverter converter = GetConverter(contract, null); + + if (reader.TokenType == JsonToken.None && !ReadForType(reader, contract, converter != null, false)) return null; - return CreateValueNonProperty(reader, objectType, GetContractSafe(objectType)); + return CreateValueNonProperty(reader, objectType, contract, converter); } private JsonSerializerProxy GetInternalSerializer() @@ -181,12 +177,30 @@ namespace Newtonsoft.Json.Serialization return token; } - private object CreateValueProperty(JsonReader reader, JsonProperty property, object target, bool gottenCurrentValue, object currentValue) + private object CreateValueProperty(JsonReader reader, JsonProperty property, JsonConverter propertyConverter, object target, bool gottenCurrentValue, object currentValue) { - JsonContract contract = GetContractSafe(property.PropertyType, currentValue); - Type objectType = property.PropertyType; + JsonContract contract; + JsonConverter converter; - JsonConverter converter = GetConverter(contract, property.MemberConverter); + if (property.PropertyContract == null) + property.PropertyContract = GetContractSafe(property.PropertyType); + + if (currentValue == null) + { + contract = property.PropertyContract; + converter = propertyConverter; + } + else + { + contract = GetContractSafe(currentValue.GetType()); + + if (contract != property.PropertyContract) + converter = GetConverter(contract, property.MemberConverter); + else + converter = propertyConverter; + } + + Type objectType = property.PropertyType; if (converter != null && converter.CanRead) { @@ -199,10 +213,8 @@ namespace Newtonsoft.Json.Serialization return CreateValueInternal(reader, objectType, contract, property, currentValue); } - private object CreateValueNonProperty(JsonReader reader, Type objectType, JsonContract contract) + private object CreateValueNonProperty(JsonReader reader, Type objectType, JsonContract contract, JsonConverter converter) { - JsonConverter converter = GetConverter(contract, null); - if (converter != null && converter.CanRead) return converter.ReadJson(reader, objectType, null, GetInternalSerializer()); @@ -522,7 +534,7 @@ namespace Newtonsoft.Json.Serialization return value.ToString(); } - private void SetPropertyValue(JsonProperty property, JsonReader reader, object target) + private void SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonReader reader, object target) { if (property.Ignored) { @@ -573,7 +585,7 @@ namespace Newtonsoft.Json.Serialization } object existingValue = (useExistingValue) ? currentValue : null; - object value = CreateValueProperty(reader, property, target, gottenCurrentValue, existingValue); + object value = CreateValueProperty(reader, property, propertyConverter, target, gottenCurrentValue, existingValue); // always set the value if useExistingValue is false, // otherwise also set it if CreateValue returns a new value compared to the currentValue @@ -651,10 +663,15 @@ namespace Newtonsoft.Json.Serialization throw new JsonSerializationException("Could not convert string '{0}' to dictionary key type '{1}'. Create a TypeConverter to convert from the string to the key type object.".FormatWith(CultureInfo.InvariantCulture, reader.Value, contract.DictionaryKeyType), ex); } - if (!ReadForType(reader, contract.DictionaryValueType, null)) + if (contract.DictionaryValueContract == null) + contract.DictionaryValueContract = GetContractSafe(contract.DictionaryValueType); + + JsonConverter dictionaryValueConverter = GetConverter(contract.DictionaryValueContract, null); + + if (!ReadForType(reader, contract.DictionaryValueContract, dictionaryValueConverter != null, false)) throw new JsonSerializationException("Unexpected end when deserializing object."); - dictionary[keyValue] = CreateValueNonProperty(reader, contract.DictionaryValueType, GetContractSafe(contract.DictionaryValueType)); + dictionary[keyValue] = CreateValueNonProperty(reader, contract.DictionaryValueType, contract.DictionaryValueContract, dictionaryValueConverter); } catch (Exception ex) { @@ -696,23 +713,6 @@ namespace Newtonsoft.Json.Serialization }); } - private bool ReadForTypeArrayHack(JsonReader reader, Type t) - { - // this is a nasty hack because calling ReadAsDecimal for example will error when we hit the end of the array - // need to think of a better way of doing this - try - { - return ReadForType(reader, t, null); - } - catch (JsonReaderException) - { - if (reader.TokenType == JsonToken.EndArray) - return true; - - throw; - } - } - private object PopulateList(IWrappedCollection wrappedList, JsonReader reader, string reference, JsonArrayContract contract) { object list = wrappedList.UnderlyingCollection; @@ -731,7 +731,10 @@ namespace Newtonsoft.Json.Serialization int initialDepth = reader.Depth; - while (ReadForTypeArrayHack(reader, contract.CollectionItemType)) + JsonContract collectionItemContract = GetContractSafe(contract.CollectionItemType); + JsonConverter collectionItemConverter = GetConverter(collectionItemContract, null); + + while (ReadForType(reader, collectionItemContract, collectionItemConverter != null, true)) { switch (reader.TokenType) { @@ -744,16 +747,20 @@ namespace Newtonsoft.Json.Serialization default: try { - object value = CreateValueNonProperty(reader, contract.CollectionItemType, GetContractSafe(contract.CollectionItemType)); + object value = CreateValueNonProperty(reader, contract.CollectionItemType, collectionItemContract, collectionItemConverter); wrappedList.Add(value); } catch (Exception ex) { if (IsErrorHandled(list, contract, wrappedList.Count, ex)) + { HandleError(reader, initialDepth); + } else + { throw; + } } break; } @@ -843,15 +850,24 @@ namespace Newtonsoft.Json.Serialization // first attempt to find a settable property, otherwise fall back to a dynamic set without type JsonProperty property = contract.Properties.GetClosestMatchProperty(memberName); + if (property != null && property.Writable && !property.Ignored) { - SetPropertyValue(property, reader, newObject); + if (property.PropertyContract == null) + property.PropertyContract = GetContractSafe(property.PropertyType); + + JsonConverter propertyConverter = GetConverter(property.PropertyContract, property.MemberConverter); + + SetPropertyValue(property, propertyConverter, reader, newObject); } else { Type t = (JsonReader.IsPrimitiveToken(reader.TokenType)) ? reader.ValueType : typeof (IDynamicMetaObjectProvider); - object value = CreateValueNonProperty(reader, t, GetContractSafe(t, null)); + JsonContract dynamicMemberContract = GetContractSafe(t); + JsonConverter dynamicMemberConverter = GetConverter(dynamicMemberContract, null); + + object value = CreateValueNonProperty(reader, t, dynamicMemberContract, dynamicMemberConverter); newObject.TrySetMember(memberName, value); } @@ -1008,11 +1024,16 @@ namespace Newtonsoft.Json.Serialization if (property != null) { - if (!ReadForType(reader, property.PropertyType, property.Converter)) + if (property.PropertyContract == null) + property.PropertyContract = GetContractSafe(property.PropertyType); + + JsonConverter propertyConverter = GetConverter(property.PropertyContract, property.MemberConverter); + + if (!ReadForType(reader, property.PropertyContract, propertyConverter != null, false)) throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); if (!property.Ignored) - propertyValues[property] = CreateValueProperty(reader, property, null, true, null); + propertyValues[property] = CreateValueProperty(reader, property, propertyConverter, null, true, null); else reader.Skip(); } @@ -1040,29 +1061,44 @@ namespace Newtonsoft.Json.Serialization return propertyValues; } - private bool ReadForType(JsonReader reader, Type t, JsonConverter propertyConverter) + private void SetSerializeInArray(JsonReader reader, bool inArray, bool enable) { + if (inArray) + reader.SetSerializeInArray(enable); + } + + private bool ReadForType(JsonReader reader, JsonContract contract, bool hasConverter, bool inArray) + { + Type t = (contract != null) ? contract.UnderlyingType : null; + // don't read properties with converters as a specific value // the value might be a string which will then get converted which will error if read as date for example - bool hasConverter = (GetConverter(GetContractSafe(t), propertyConverter) != null); - if (hasConverter) return reader.Read(); if (t == typeof (byte[])) { + SetSerializeInArray(reader, inArray, true); reader.ReadAsBytes(); + SetSerializeInArray(reader, inArray, false); + return true; } else if ((t == typeof (decimal) || t == typeof (decimal?))) { + SetSerializeInArray(reader, inArray, true); reader.ReadAsDecimal(); + SetSerializeInArray(reader, inArray, false); + return true; } #if !NET20 else if ((t == typeof (DateTimeOffset) || t == typeof (DateTimeOffset?))) { + SetSerializeInArray(reader, inArray, true); reader.ReadAsDateTimeOffset(); + SetSerializeInArray(reader, inArray, false); + return true; } #endif @@ -1110,12 +1146,17 @@ namespace Newtonsoft.Json.Serialization continue; } - if (!ReadForType(reader, property.PropertyType, property.Converter)) + if (property.PropertyContract == null) + property.PropertyContract = GetContractSafe(property.PropertyType); + + JsonConverter propertyConverter = GetConverter(property.PropertyContract, property.MemberConverter); + + if (!ReadForType(reader, property.PropertyContract, propertyConverter != null, false)) throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); SetPropertyPresence(reader, property, propertiesPresence); - SetPropertyValue(property, reader, newObject); + SetPropertyValue(property, propertyConverter, reader, newObject); } catch (Exception ex) { diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index a28c55c7..e0d87987 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -123,14 +123,14 @@ namespace Newtonsoft.Json.Serialization { SerializeConvertable(writer, converter, value, valueContract); } - else if (valueContract is JsonPrimitiveContract) - { - SerializePrimitive(writer, value, (JsonPrimitiveContract)valueContract, member, collectionValueContract); - } else if (valueContract is JsonStringContract) { SerializeString(writer, value, (JsonStringContract)valueContract); } + else if (valueContract is JsonPrimitiveContract) + { + SerializePrimitive(writer, value, (JsonPrimitiveContract)valueContract, member, collectionValueContract); + } else if (valueContract is JsonObjectContract) { SerializeObject(writer, value, (JsonObjectContract)valueContract, member, collectionValueContract); @@ -333,8 +333,11 @@ namespace Newtonsoft.Json.Serialization { if (!property.Ignored && property.Readable && ShouldSerialize(property, value) && IsSpecified(property, value)) { + if (property.PropertyContract == null) + property.PropertyContract = Serializer.ContractResolver.ResolveContract(property.PropertyType); + object memberValue = property.ValueProvider.GetValue(value); - JsonContract memberContract = GetContractSafe(memberValue); + JsonContract memberContract = (property.PropertyContract.UnderlyingType.IsSealed) ? property.PropertyContract : GetContractSafe(memberValue); WriteMemberInfoProperty(writer, memberValue, property, memberContract); } @@ -419,7 +422,10 @@ namespace Newtonsoft.Json.Serialization writer.WritePropertyName(JsonTypeReflector.ArrayValuesPropertyName); } - JsonContract childValuesContract = Serializer.ContractResolver.ResolveContract(contract.CollectionItemType ?? typeof(object)); + if (contract.CollectionItemContract == null) + contract.CollectionItemContract = Serializer.ContractResolver.ResolveContract(contract.CollectionItemType ?? typeof(object)); + + JsonContract collectionItemValueContract = (contract.CollectionItemContract.UnderlyingType.IsSealed) ? contract.CollectionItemContract : null; writer.WriteStartArray(); @@ -431,7 +437,7 @@ namespace Newtonsoft.Json.Serialization { try { - JsonContract valueContract = GetContractSafe(value); + JsonContract valueContract = collectionItemValueContract ?? GetContractSafe(value); if (ShouldWriteReference(value, null, valueContract)) { @@ -441,7 +447,7 @@ namespace Newtonsoft.Json.Serialization { if (CheckForCircularReference(value, null, contract)) { - SerializeValue(writer, value, valueContract, null, childValuesContract); + SerializeValue(writer, value, valueContract, null, contract.CollectionItemContract); } } } @@ -582,7 +588,10 @@ namespace Newtonsoft.Json.Serialization WriteTypeProperty(writer, values.UnderlyingDictionary.GetType()); } - JsonContract childValuesContract = Serializer.ContractResolver.ResolveContract(contract.DictionaryValueType ?? typeof(object)); + if (contract.DictionaryValueContract == null) + contract.DictionaryValueContract = Serializer.ContractResolver.ResolveContract(contract.DictionaryValueType ?? typeof(object)); + + JsonContract dictionaryValueContract = (contract.DictionaryValueContract.UnderlyingType.IsSealed) ? contract.DictionaryValueContract : null; int initialDepth = writer.Top; @@ -600,7 +609,7 @@ namespace Newtonsoft.Json.Serialization try { object value = entry.Value; - JsonContract valueContract = GetContractSafe(value); + JsonContract valueContract = dictionaryValueContract ?? GetContractSafe(value); if (ShouldWriteReference(value, null, valueContract)) { @@ -614,7 +623,7 @@ namespace Newtonsoft.Json.Serialization writer.WritePropertyName(propertyName); - SerializeValue(writer, value, valueContract, null, childValuesContract); + SerializeValue(writer, value, valueContract, null, contract.DictionaryValueContract); } } catch (Exception ex) diff --git a/Src/Newtonsoft.Json/Serialization/JsonStringContract.cs b/Src/Newtonsoft.Json/Serialization/JsonStringContract.cs index 1f3f47cd..c608d9ea 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonStringContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonStringContract.cs @@ -33,7 +33,7 @@ namespace Newtonsoft.Json.Serialization /// /// Contract details for a used by the . /// - public class JsonStringContract : JsonContract + public class JsonStringContract : JsonPrimitiveContract { /// /// Initializes a new instance of the class. diff --git a/Src/Newtonsoft.Json/Utilities/StringBuffer.cs b/Src/Newtonsoft.Json/Utilities/StringBuffer.cs index 747e6d0a..321caa40 100644 --- a/Src/Newtonsoft.Json/Utilities/StringBuffer.cs +++ b/Src/Newtonsoft.Json/Utilities/StringBuffer.cs @@ -57,14 +57,22 @@ namespace Newtonsoft.Json.Utilities { // test if the buffer array is large enough to take the value if (_position == _buffer.Length) - { EnsureSize(1); - } // set value and increment poisition _buffer[_position++] = value; } + public void Append(char[] buffer, int startIndex, int count) + { + if (_position + count >= _buffer.Length) + EnsureSize(count); + + Array.Copy(buffer, startIndex, _buffer, _position, count); + + _position += count; + } + public void Clear() { _buffer = _emptyBuffer; diff --git a/Src/Newtonsoft.Json/Utilities/StringReference.cs b/Src/Newtonsoft.Json/Utilities/StringReference.cs new file mode 100644 index 00000000..ddcdff21 --- /dev/null +++ b/Src/Newtonsoft.Json/Utilities/StringReference.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Newtonsoft.Json.Utilities +{ + internal struct StringReference + { + private readonly char[] _chars; + private readonly int _startIndex; + private readonly int _length; + + public char[] Chars + { + get { return _chars; } + } + + public int StartIndex + { + get { return _startIndex; } + } + + public int Length + { + get { return _length; } + } + + public StringReference(char[] chars, int startIndex, int length) + { + _chars = chars; + _startIndex = startIndex; + _length = length; + } + + public override string ToString() + { + return new string(_chars, _startIndex, _length); + } + } +} \ No newline at end of file