From 0d6ad0b76ed3b97275122a3bda8171b31cac53e8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 23 Feb 2012 23:07:41 +1300 Subject: -Added Path to JsonReader/JsonWriter/ErrorContext with the JSON path of the current position -Changed collection type on JsonArrayContract to public -Changed dictionary key type and dictionary value type on JsonDictionaryContract to public -Fixed error handling when failing to parse array content -Fixed error handling when there are missing required properties --- Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs | 152 ++++++++++------ Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs | 163 ++++++++++++++++- .../Properties/AssemblyInfo.cs | 2 +- .../SerializationErrorHandlingTests.cs | 93 +++++++--- .../TestObjects/VersionKeyedCollection.cs | 2 +- Src/Newtonsoft.Json/JsonReader.cs | 197 ++++++++++++++++----- Src/Newtonsoft.Json/JsonWriter.cs | 197 +++++++++++++++------ Src/Newtonsoft.Json/Properties/AssemblyInfo.cs | 2 +- Src/Newtonsoft.Json/Serialization/ErrorContext.cs | 8 +- .../Serialization/JsonArrayContract.cs | 7 +- .../Serialization/JsonDictionaryContract.cs | 12 +- .../Serialization/JsonSerializerInternalBase.cs | 8 +- .../Serialization/JsonSerializerInternalReader.cs | 173 ++++++++++-------- .../Serialization/JsonSerializerInternalWriter.cs | 6 +- 14 files changed, 762 insertions(+), 260 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index bda2f3e..3488f99 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -272,75 +272,127 @@ namespace Newtonsoft.Json.Tests { string input = @"{ value:'Purple', - array:[1,2], - subobject:{prop:1} + array:[1,2,new Date(1)], + subobject:{prop:1,proparray:[1]} }"; StringReader sr = new StringReader(input); - using (JsonReader jsonReader = new JsonTextReader(sr)) + using (JsonReader reader = new JsonTextReader(sr)) { - Assert.AreEqual(0, jsonReader.Depth); + Assert.AreEqual(0, reader.Depth); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.StartObject); - Assert.AreEqual(0, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.StartObject); + Assert.AreEqual(0, reader.Depth); + Assert.AreEqual("", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); - Assert.AreEqual(1, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(1, reader.Depth); + Assert.AreEqual("value", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.String); - Assert.AreEqual(jsonReader.Value, @"Purple"); - Assert.AreEqual(jsonReader.QuoteChar, '\''); - Assert.AreEqual(1, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.String); + Assert.AreEqual(reader.Value, @"Purple"); + Assert.AreEqual(reader.QuoteChar, '\''); + Assert.AreEqual(1, reader.Depth); + Assert.AreEqual("value", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); - Assert.AreEqual(1, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(1, reader.Depth); + Assert.AreEqual("array", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.StartArray); - Assert.AreEqual(1, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.StartArray); + Assert.AreEqual(1, reader.Depth); + Assert.AreEqual("array", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.Integer); - Assert.AreEqual(1, jsonReader.Value); - Assert.AreEqual(2, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.Integer); + Assert.AreEqual(1, reader.Value); + Assert.AreEqual(2, reader.Depth); + Assert.AreEqual("array[0]", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.Integer); - Assert.AreEqual(2, jsonReader.Value); - Assert.AreEqual(2, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.Integer); + Assert.AreEqual(2, reader.Value); + Assert.AreEqual(2, reader.Depth); + Assert.AreEqual("array[1]", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.EndArray); - Assert.AreEqual(1, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.StartConstructor); + Assert.AreEqual("Date", reader.Value); + Assert.AreEqual(2, reader.Depth); + Assert.AreEqual("array[2]", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); - Assert.AreEqual(1, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.Integer); + Assert.AreEqual(1, reader.Value); + Assert.AreEqual(3, reader.Depth); + Assert.AreEqual("array[2][0]", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.StartObject); - Assert.AreEqual(1, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.EndConstructor); + Assert.AreEqual(null, reader.Value); + Assert.AreEqual(2, reader.Depth); + Assert.AreEqual("array[2]", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); - Assert.AreEqual(2, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.EndArray); + Assert.AreEqual(1, reader.Depth); + Assert.AreEqual("array", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.Integer); - Assert.AreEqual(2, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(1, reader.Depth); + Assert.AreEqual("subobject", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.EndObject); - Assert.AreEqual(1, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.StartObject); + Assert.AreEqual(1, reader.Depth); + Assert.AreEqual("subobject", reader.Path); - jsonReader.Read(); - Assert.AreEqual(jsonReader.TokenType, JsonToken.EndObject); - Assert.AreEqual(0, jsonReader.Depth); + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(2, reader.Depth); + Assert.AreEqual("subobject.prop", reader.Path); + + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.Integer); + Assert.AreEqual(2, reader.Depth); + Assert.AreEqual("subobject.prop", reader.Path); + + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(2, reader.Depth); + Assert.AreEqual("subobject.proparray", reader.Path); + + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.StartArray); + Assert.AreEqual(2, reader.Depth); + Assert.AreEqual("subobject.proparray", reader.Path); + + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.Integer); + Assert.AreEqual(3, reader.Depth); + Assert.AreEqual("subobject.proparray[0]", reader.Path); + + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.EndArray); + Assert.AreEqual(2, reader.Depth); + Assert.AreEqual("subobject.proparray", reader.Path); + + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.EndObject); + Assert.AreEqual(1, reader.Depth); + Assert.AreEqual("subobject", reader.Path); + + reader.Read(); + Assert.AreEqual(reader.TokenType, JsonToken.EndObject); + Assert.AreEqual(0, reader.Depth); + Assert.AreEqual("", reader.Path); } } diff --git a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs index 614bd08..97323a1 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs @@ -199,6 +199,85 @@ namespace Newtonsoft.Json.Tests Assert.AreEqual(expected, result); } + [Test] + public void WriteEnd() + { + StringBuilder sb = new StringBuilder(); + StringWriter sw = new StringWriter(sb); + + using (JsonWriter jsonWriter = new JsonTextWriter(sw)) + { + jsonWriter.Formatting = Formatting.Indented; + + jsonWriter.WriteStartObject(); + jsonWriter.WritePropertyName("CPU"); + jsonWriter.WriteValue("Intel"); + jsonWriter.WritePropertyName("PSU"); + jsonWriter.WriteValue("500W"); + jsonWriter.WritePropertyName("Drives"); + jsonWriter.WriteStartArray(); + jsonWriter.WriteValue("DVD read/writer"); + jsonWriter.WriteComment("(broken)"); + jsonWriter.WriteValue("500 gigabyte hard drive"); + jsonWriter.WriteValue("200 gigabype hard drive"); + jsonWriter.WriteEndObject(); + Assert.AreEqual(WriteState.Start, jsonWriter.WriteState); + } + + string expected = @"{ + ""CPU"": ""Intel"", + ""PSU"": ""500W"", + ""Drives"": [ + ""DVD read/writer"" + /*(broken)*/, + ""500 gigabyte hard drive"", + ""200 gigabype hard drive"" + ] +}"; + string result = sb.ToString(); + + Assert.AreEqual(expected, result); + } + + [Test] + public void CloseWithRemainingContent() + { + StringBuilder sb = new StringBuilder(); + StringWriter sw = new StringWriter(sb); + + using (JsonWriter jsonWriter = new JsonTextWriter(sw)) + { + jsonWriter.Formatting = Formatting.Indented; + + jsonWriter.WriteStartObject(); + jsonWriter.WritePropertyName("CPU"); + jsonWriter.WriteValue("Intel"); + jsonWriter.WritePropertyName("PSU"); + jsonWriter.WriteValue("500W"); + jsonWriter.WritePropertyName("Drives"); + jsonWriter.WriteStartArray(); + jsonWriter.WriteValue("DVD read/writer"); + jsonWriter.WriteComment("(broken)"); + jsonWriter.WriteValue("500 gigabyte hard drive"); + jsonWriter.WriteValue("200 gigabype hard drive"); + jsonWriter.Close(); + } + + string expected = @"{ + ""CPU"": ""Intel"", + ""PSU"": ""500W"", + ""Drives"": [ + ""DVD read/writer"" + /*(broken)*/, + ""500 gigabyte hard drive"", + ""200 gigabype hard drive"" + ] +}"; + string result = sb.ToString(); + + Assert.AreEqual(expected, result); + } + [Test] public void Indenting() { @@ -222,6 +301,7 @@ namespace Newtonsoft.Json.Tests jsonWriter.WriteValue("200 gigabype hard drive"); jsonWriter.WriteEnd(); jsonWriter.WriteEndObject(); + Assert.AreEqual(WriteState.Start, jsonWriter.WriteState); } // { @@ -247,9 +327,6 @@ namespace Newtonsoft.Json.Tests }"; string result = sb.ToString(); - Console.WriteLine("Indenting"); - Console.WriteLine(result); - Assert.AreEqual(expected, result); } @@ -265,27 +342,34 @@ namespace Newtonsoft.Json.Tests jsonWriter.WriteStartObject(); Assert.AreEqual(WriteState.Object, jsonWriter.WriteState); + Assert.AreEqual("", jsonWriter.Path); jsonWriter.WritePropertyName("CPU"); Assert.AreEqual(WriteState.Property, jsonWriter.WriteState); + Assert.AreEqual("CPU", jsonWriter.Path); jsonWriter.WriteValue("Intel"); Assert.AreEqual(WriteState.Object, jsonWriter.WriteState); + Assert.AreEqual("CPU", jsonWriter.Path); jsonWriter.WritePropertyName("Drives"); Assert.AreEqual(WriteState.Property, jsonWriter.WriteState); + Assert.AreEqual("Drives", jsonWriter.Path); jsonWriter.WriteStartArray(); Assert.AreEqual(WriteState.Array, jsonWriter.WriteState); jsonWriter.WriteValue("DVD read/writer"); Assert.AreEqual(WriteState.Array, jsonWriter.WriteState); + Assert.AreEqual("Drives[0]", jsonWriter.Path); jsonWriter.WriteEnd(); Assert.AreEqual(WriteState.Object, jsonWriter.WriteState); + Assert.AreEqual("Drives", jsonWriter.Path); jsonWriter.WriteEndObject(); Assert.AreEqual(WriteState.Start, jsonWriter.WriteState); + Assert.AreEqual("", jsonWriter.Path); } } @@ -620,6 +704,79 @@ _____'propertyName': NaN Assert.AreEqual(expected, result); } + [Test] + public void Path() + { + StringBuilder sb = new StringBuilder(); + StringWriter sw = new StringWriter(sb); + + string text = "Hello world."; + byte[] data = Encoding.UTF8.GetBytes(text); + + using (JsonTextWriter writer = new JsonTextWriter(sw)) + { + writer.Formatting = Formatting.Indented; + + writer.WriteStartArray(); + Assert.AreEqual("", writer.Path); + writer.WriteStartObject(); + Assert.AreEqual("[0]", writer.Path); + writer.WritePropertyName("Property1"); + Assert.AreEqual("[0].Property1", writer.Path); + writer.WriteStartArray(); + Assert.AreEqual("[0].Property1", writer.Path); + writer.WriteValue(1); + Assert.AreEqual("[0].Property1[0]", writer.Path); + writer.WriteStartArray(); + Assert.AreEqual("[0].Property1[1]", writer.Path); + writer.WriteStartArray(); + Assert.AreEqual("[0].Property1[1][0]", writer.Path); + writer.WriteStartArray(); + Assert.AreEqual("[0].Property1[1][0][0]", writer.Path); + writer.WriteEndObject(); + Assert.AreEqual("[0]", writer.Path); + writer.WriteStartObject(); + Assert.AreEqual("[1]", writer.Path); + writer.WritePropertyName("Property2"); + Assert.AreEqual("[1].Property2", writer.Path); + writer.WriteStartConstructor("Constructor1"); + Assert.AreEqual("[1].Property2", writer.Path); + writer.WriteNull(); + Assert.AreEqual("[1].Property2[0]", writer.Path); + writer.WriteStartArray(); + Assert.AreEqual("[1].Property2[1]", writer.Path); + writer.WriteValue(1); + Assert.AreEqual("[1].Property2[1][0]", writer.Path); + writer.WriteEnd(); + Assert.AreEqual("[1].Property2[1]", writer.Path); + writer.WriteEndObject(); + Assert.AreEqual("[1]", writer.Path); + writer.WriteEndArray(); + Assert.AreEqual("", writer.Path); + } + + Assert.AreEqual(@"[ + { + ""Property1"": [ + 1, + [ + [ + [] + ] + ] + ] + }, + { + ""Property2"": new Constructor1( + null, + [ + 1 + ] + ) + } +]", sb.ToString()); + } + [Test] public void BuildStateArray() { diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 6c451eb..f34065e 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -72,5 +72,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.0.8.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.0.8.14618")] +[assembly: AssemblyFileVersion("4.0.8.14623")] #endif diff --git a/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs index 61e76d8..a6a4ec5 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs @@ -57,7 +57,7 @@ namespace Newtonsoft.Json.Tests.Serialization VersionKeyedCollection c = JsonConvert.DeserializeObject(json); Assert.AreEqual(1, c.Count); Assert.AreEqual(1, c.Messages.Count); - Assert.AreEqual("Error message for member 1 = An item with the same key has already been added.", c.Messages[0]); + Assert.AreEqual("[1] - Error message for member 1 = An item with the same key has already been added.", c.Messages[0]); } [Test] @@ -161,7 +161,8 @@ namespace Newtonsoft.Json.Tests.Serialization { List errors = new List(); - List c = JsonConvert.DeserializeObject>(@"[ + List c = JsonConvert.DeserializeObject>( + @"[ ""2009-09-09T00:00:00Z"", ""I am not a date and will error!"", [ @@ -175,10 +176,10 @@ namespace Newtonsoft.Json.Tests.Serialization { Error = delegate(object sender, ErrorEventArgs args) { - errors.Add(args.ErrorContext.Error.Message); + errors.Add(args.ErrorContext.Path + " - " + args.ErrorContext.Member + " - " + args.ErrorContext.Error.Message); args.ErrorContext.Handled = true; }, - Converters = { new IsoDateTimeConverter() } + Converters = {new IsoDateTimeConverter()} }); // 2009-09-09T00:00:00Z @@ -196,12 +197,12 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(3, errors.Count); #if !(NET20 || NET35 || WINDOWS_PHONE) - Assert.AreEqual("The string was not recognized as a valid DateTime. There is an unknown word starting at index 0.", errors[0]); + Assert.AreEqual("[1] - 1 - The string was not recognized as a valid DateTime. There is an unknown word starting at index 0.", errors[0]); #else - Assert.AreEqual("The string was not recognized as a valid DateTime. There is a unknown word starting at index 0.", errors[0]); + Assert.AreEqual("[1] - 1 - The string was not recognized as a valid DateTime. There is a unknown word starting at index 0.", errors[0]); #endif - Assert.AreEqual("Unexpected token parsing date. Expected String, got StartArray.", errors[1]); - Assert.AreEqual("Cannot convert null value to System.DateTime.", errors[2]); + Assert.AreEqual("[2] - 2 - Unexpected token parsing date. Expected String, got StartArray.", errors[1]); + Assert.AreEqual("[4] - 4 - Cannot convert null value to System.DateTime.", errors[2]); } [Test] @@ -209,7 +210,8 @@ namespace Newtonsoft.Json.Tests.Serialization { bool eventErrorHandlerCalled = false; - DateTimeErrorObjectCollection c = JsonConvert.DeserializeObject(@"[ + DateTimeErrorObjectCollection c = JsonConvert.DeserializeObject( + @"[ ""2009-09-09T00:00:00Z"", ""kjhkjhkjhkjh"", [ @@ -220,13 +222,13 @@ namespace Newtonsoft.Json.Tests.Serialization ""2000-12-01T00:00:00Z"" ]", new JsonSerializerSettings - { - Error = (s, a) => eventErrorHandlerCalled = true, - Converters = + { + Error = (s, a) => eventErrorHandlerCalled = true, + Converters = { new IsoDateTimeConverter() } - }); + }); Assert.AreEqual(3, c.Count); Assert.AreEqual(new DateTime(2009, 9, 9, 0, 0, 0, DateTimeKind.Utc), c[0]); @@ -270,25 +272,76 @@ namespace Newtonsoft.Json.Tests.Serialization string json = @"[[""kjhkjhkjhkjh""]]"; + Exception e = null; try { JsonSerializer serializer = new JsonSerializer(); serializer.Error += delegate(object sender, ErrorEventArgs args) - { - // only log an error once - if (args.CurrentObject == args.ErrorContext.OriginalObject) - errors.Add(args.ErrorContext.Error.Message); - }; + { + // only log an error once + if (args.CurrentObject == args.ErrorContext.OriginalObject) + errors.Add(args.ErrorContext.Path + " - " + args.ErrorContext.Member + " - " + args.ErrorContext.Error.Message); + }; serializer.Deserialize(new StringReader(json), typeof(List>)); } catch (Exception ex) { - Console.WriteLine(ex.Message); + e = ex; } + Assert.AreEqual(@"Error converting value ""kjhkjhkjhkjh"" to type 'System.DateTime'. Line 1, position 16.", e.Message); + Assert.AreEqual(1, errors.Count); - Assert.AreEqual(@"Error converting value ""kjhkjhkjhkjh"" to type 'System.DateTime'. Line 1, position 16.", errors[0]); + Assert.AreEqual(@"[0][0] - 0 - Error converting value ""kjhkjhkjhkjh"" to type 'System.DateTime'. Line 1, position 16.", errors[0]); + } + + [Test] + public void MultipleRequiredPropertyErrors() + { + string json = "{}"; + List errors = new List(); + JsonSerializer serializer = new JsonSerializer(); + serializer.Error += delegate(object sender, ErrorEventArgs args) + { + errors.Add(args.ErrorContext.Path + " - " + args.ErrorContext.Member + " - " + args.ErrorContext.Error.Message); + args.ErrorContext.Handled = true; + }; + serializer.Deserialize(new JsonTextReader(new StringReader(json)), typeof (MyTypeWithRequiredMembers)); + + Assert.AreEqual(2, errors.Count); + Assert.AreEqual(" - Required1 - Required property 'Required1' not found in JSON. Line 1, position 2.", errors[0]); + Assert.AreEqual(" - Required2 - Required property 'Required2' not found in JSON. Line 1, position 2.", errors[1]); + } + + [Test] + public void HandlingArrayErrors() + { + string json = "[\"a\",\"b\",\"45\",34]"; + + List errors = new List(); + + JsonSerializer serializer = new JsonSerializer(); + serializer.Error += delegate(object sender, ErrorEventArgs args) + { + errors.Add(args.ErrorContext.Path + " - " + args.ErrorContext.Member + " - " + args.ErrorContext.Error.Message); + args.ErrorContext.Handled = true; + }; + + serializer.Deserialize(new JsonTextReader(new StringReader(json)), typeof(int[])); + + Assert.AreEqual(2, errors.Count); + Assert.AreEqual("[0] - 0 - Could not convert string to integer: a. Line 1, position 4.", errors[0]); + Assert.AreEqual("[1] - 1 - Could not convert string to integer: b. Line 1, position 8.", errors[1]); } } + + [JsonObject] + public class MyTypeWithRequiredMembers + { + [JsonProperty(Required = Required.AllowNull)] + public string Required1; + [JsonProperty(Required = Required.AllowNull)] + public string Required2; + } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/VersionKeyedCollection.cs b/Src/Newtonsoft.Json.Tests/TestObjects/VersionKeyedCollection.cs index 647b29a..66a84d6 100644 --- a/Src/Newtonsoft.Json.Tests/TestObjects/VersionKeyedCollection.cs +++ b/Src/Newtonsoft.Json.Tests/TestObjects/VersionKeyedCollection.cs @@ -50,7 +50,7 @@ namespace Newtonsoft.Json.Tests.TestObjects [OnError] internal void OnErrorMethod(StreamingContext context, ErrorContext errorContext) { - Messages.Add("Error message for member " + errorContext.Member + " = " + errorContext.Error.Message); + Messages.Add(errorContext.Path + " - Error message for member " + errorContext.Member + " = " + errorContext.Error.Message); errorContext.Handled = true; } diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index 6d80c6e..edcc989 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -27,11 +27,78 @@ using System; using System.Collections.Generic; using System.IO; using System.Globalization; +using System.Linq; +using System.Text; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json { + internal enum JsonContainerType + { + None, + Object, + Array, + Constructor + } + + internal struct JsonPosition + { + internal JsonContainerType Type; + internal int? Position; + internal string PropertyName; + + internal void WriteTo(StringBuilder sb) + { + switch (Type) + { + case JsonContainerType.Object: + if (PropertyName != null) + { + if (sb.Length > 0) + sb.Append("."); + sb.Append(PropertyName); + } + break; + case JsonContainerType.Array: + case JsonContainerType.Constructor: + if (Position != null) + { + sb.Append("["); + sb.Append(Position); + sb.Append("]"); + } + break; + } + } + + internal bool InsideContainer() + { + switch (Type) + { + case JsonContainerType.Object: + return (PropertyName != null); + case JsonContainerType.Array: + case JsonContainerType.Constructor: + return (Position != null); + } + + return false; + } + + internal static string BuildPath(IEnumerable positions) + { + StringBuilder sb = new StringBuilder(); + + foreach (JsonPosition state in positions) + { + state.WriteTo(sb); + } + + return sb.ToString(); + } + } + /// /// Represents a reader that provides fast, non-cached, forward-only access to serialized Json data. /// @@ -101,7 +168,7 @@ namespace Newtonsoft.Json private object _value; private char _quoteChar; internal State _currentState; - private JTokenType _currentTypeContext; + private JsonPosition _currentPosition; private CultureInfo _culture; /// @@ -113,9 +180,7 @@ namespace Newtonsoft.Json get { return _currentState; } } - private int _top; - - private readonly List _stack; + private readonly List _stack; /// /// Gets or sets a value indicating whether the underlying stream or @@ -137,7 +202,7 @@ namespace Newtonsoft.Json } /// - /// Gets the type of the current Json token. + /// Gets the type of the current JSON token. /// public virtual JsonToken TokenType { @@ -145,7 +210,7 @@ namespace Newtonsoft.Json } /// - /// Gets the text value of the current Json token. + /// Gets the text value of the current JSON token. /// public virtual object Value { @@ -153,7 +218,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 virtual Type ValueType { @@ -168,11 +233,11 @@ namespace Newtonsoft.Json { get { - int depth = _top - 1; - if (IsStartToken(TokenType)) - return depth - 1; - else + int depth = _stack.Count; + if (IsStartToken(TokenType) || _currentPosition.Type == JsonContainerType.None) return depth; + else + return depth + 1; } } @@ -185,39 +250,71 @@ namespace Newtonsoft.Json set { _culture = value; } } + /// + /// Gets the path of the current JSON token. + /// + public string Path + { + get + { + if (_currentPosition.Type == JsonContainerType.None) + return string.Empty; + + return JsonPosition.BuildPath(_stack.Concat(new[] { _currentPosition })); + } + } + /// /// Initializes a new instance of the class with the specified . /// protected JsonReader() { _currentState = State.Start; - _stack = new List(); + _stack = new List(4); CloseInput = true; - - Push(JTokenType.None); } - private void Push(JTokenType value) + private void Push(JsonContainerType value) { - _stack.Add(value); - _top++; - _currentTypeContext = value; + UpdateScopeWithFinishedValue(); + + if (_currentPosition.Type == JsonContainerType.None) + { + _currentPosition.Type = value; + } + else + { + _stack.Add(_currentPosition); + var state = new JsonPosition + { + Type = value + }; + _currentPosition = state; + } } - private JTokenType Pop() + private JsonContainerType Pop() { - JTokenType value = Peek(); - _stack.RemoveAt(_stack.Count - 1); - _top--; - _currentTypeContext = _stack[_top - 1]; + JsonPosition oldPosition; + if (_stack.Count > 0) + { + oldPosition = _currentPosition; + _currentPosition = _stack[_stack.Count - 1]; + _stack.RemoveAt(_stack.Count - 1); + } + else + { + oldPosition = _currentPosition; + _currentPosition = new JsonPosition(); + } - return value; + return oldPosition.Type; } - private JTokenType Peek() + private JsonContainerType Peek() { - return _currentTypeContext; + return _currentPosition.Type; } /// @@ -292,15 +389,15 @@ namespace Newtonsoft.Json { case JsonToken.StartObject: _currentState = State.ObjectStart; - Push(JTokenType.Object); + Push(JsonContainerType.Object); break; case JsonToken.StartArray: _currentState = State.ArrayStart; - Push(JTokenType.Array); + Push(JsonContainerType.Array); break; case JsonToken.StartConstructor: _currentState = State.ConstructorStart; - Push(JTokenType.Constructor); + Push(JsonContainerType.Constructor); break; case JsonToken.EndObject: ValidateEnd(JsonToken.EndObject); @@ -313,6 +410,8 @@ namespace Newtonsoft.Json break; case JsonToken.PropertyName: _currentState = State.Property; + + _currentPosition.PropertyName = (string) value; break; case JsonToken.Undefined: case JsonToken.Integer: @@ -323,21 +422,35 @@ namespace Newtonsoft.Json case JsonToken.String: case JsonToken.Raw: case JsonToken.Bytes: - _currentState = (Peek() != JTokenType.None) ? State.PostValue : State.Finished; + _currentState = (Peek() != JsonContainerType.None) ? State.PostValue : State.Finished; + + UpdateScopeWithFinishedValue(); break; } _value = value; } + private void UpdateScopeWithFinishedValue() + { + if (_currentPosition.Type == JsonContainerType.Array + || _currentPosition.Type == JsonContainerType.Constructor) + { + if (_currentPosition.Position == null) + _currentPosition.Position = 0; + else + _currentPosition.Position++; + } + } + private void ValidateEnd(JsonToken endToken) { - JTokenType currentObject = Pop(); + JsonContainerType currentObject = Pop(); 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; + _currentState = (Peek() != JsonContainerType.None) ? State.PostValue : State.Finished; } /// @@ -345,20 +458,20 @@ namespace Newtonsoft.Json /// protected void SetStateBasedOnCurrent() { - JTokenType currentObject = Peek(); + JsonContainerType currentObject = Peek(); switch (currentObject) { - case JTokenType.Object: + case JsonContainerType.Object: _currentState = State.Object; break; - case JTokenType.Array: + case JsonContainerType.Array: _currentState = State.Array; break; - case JTokenType.Constructor: + case JsonContainerType.Constructor: _currentState = State.Constructor; break; - case JTokenType.None: + case JsonContainerType.None: _currentState = State.Finished; break; default: @@ -413,16 +526,16 @@ namespace Newtonsoft.Json } } - private JTokenType GetTypeForCloseToken(JsonToken token) + private JsonContainerType GetTypeForCloseToken(JsonToken token) { switch (token) { case JsonToken.EndObject: - return JTokenType.Object; + return JsonContainerType.Object; case JsonToken.EndArray: - return JTokenType.Array; + return JsonContainerType.Array; case JsonToken.EndConstructor: - return JTokenType.Constructor; + return JsonContainerType.Constructor; default: throw new JsonReaderException("Not a valid close JsonToken: {0}".FormatWith(CultureInfo.InvariantCulture, token)); } @@ -497,4 +610,4 @@ namespace Newtonsoft.Json return message; } } -} +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonWriter.cs b/Src/Newtonsoft.Json/JsonWriter.cs index aa77b0f..a4e9289 100644 --- a/Src/Newtonsoft.Json/JsonWriter.cs +++ b/Src/Newtonsoft.Json/JsonWriter.cs @@ -160,9 +160,8 @@ namespace Newtonsoft.Json StateArray = BuildStateArray(); } - private int _top; - - private readonly List _stack; + private readonly List _stack; + private JsonPosition _currentPosition; private State _currentState; private Formatting _formatting; @@ -182,9 +181,31 @@ namespace Newtonsoft.Json /// The top. protected internal int Top { - get { return _top; } + get + { + int depth = _stack.Count; + if (Peek() != JsonContainerType.None) + depth++; + + return depth; + } } + internal string ContainerPath + { + get + { + if (_currentPosition.Type == JsonContainerType.None) + return string.Empty; + + IEnumerable positions = (_currentPosition.InsideContainer()) + ? _stack + : _stack.Concat(new[] { _currentPosition }); + + return JsonPosition.BuildPath(positions); + } + } + /// /// Gets the state of the writer. /// @@ -217,6 +238,20 @@ namespace Newtonsoft.Json } } + /// + /// Gets the path of the writer. + /// + public string Path + { + get + { + if (_currentPosition.Type == JsonContainerType.None) + return string.Empty; + + return JsonPosition.BuildPath(_stack.Concat(new[] { _currentPosition })); + } + } + /// /// Indicates how the output is formatted. /// @@ -231,34 +266,65 @@ namespace Newtonsoft.Json /// protected JsonWriter() { - _stack = new List(8); - _stack.Add(JTokenType.None); + _stack = new List(4); _currentState = State.Start; _formatting = Formatting.None; CloseOutput = true; } - private void Push(JTokenType value) + private void UpdateScopeWithFinishedValue() + { + if (_currentPosition.Type == JsonContainerType.Array + || _currentPosition.Type == JsonContainerType.Constructor) + { + if (_currentPosition.Position == null) + _currentPosition.Position = 0; + else + _currentPosition.Position++; + } + } + + private void Push(JsonContainerType value) { - _top++; - if (_stack.Count <= _top) - _stack.Add(value); + UpdateScopeWithFinishedValue(); + + if (_currentPosition.Type == JsonContainerType.None) + { + _currentPosition.Type = value; + } else - _stack[_top] = value; + { + _stack.Add(_currentPosition); + var state = new JsonPosition + { + Type = value + }; + _currentPosition = state; + } } - private JTokenType Pop() + private JsonContainerType Pop() { - JTokenType value = Peek(); - _top--; + JsonPosition oldPosition; + if (_stack.Count > 0) + { + oldPosition = _currentPosition; + _currentPosition = _stack[_stack.Count - 1]; + _stack.RemoveAt(_stack.Count - 1); + } + else + { + oldPosition = _currentPosition; + _currentPosition = new JsonPosition(); + } - return value; + return oldPosition.Type; } - private JTokenType Peek() + private JsonContainerType Peek() { - return _stack[_top]; + return _currentPosition.Type; } /// @@ -280,7 +346,7 @@ namespace Newtonsoft.Json public virtual void WriteStartObject() { AutoComplete(JsonToken.StartObject); - Push(JTokenType.Object); + Push(JsonContainerType.Object); } /// @@ -297,7 +363,7 @@ namespace Newtonsoft.Json public virtual void WriteStartArray() { AutoComplete(JsonToken.StartArray); - Push(JTokenType.Array); + Push(JsonContainerType.Array); } /// @@ -315,7 +381,7 @@ namespace Newtonsoft.Json public virtual void WriteStartConstructor(string name) { AutoComplete(JsonToken.StartConstructor); - Push(JTokenType.Constructor); + Push(JsonContainerType.Constructor); } /// @@ -332,6 +398,7 @@ namespace Newtonsoft.Json /// The name of the property. public virtual void WritePropertyName(string name) { + _currentPosition.PropertyName = name; AutoComplete(JsonToken.PropertyName); } @@ -482,17 +549,17 @@ namespace Newtonsoft.Json } } - private void WriteEnd(JTokenType type) + private void WriteEnd(JsonContainerType type) { switch (type) { - case JTokenType.Object: + case JsonContainerType.Object: WriteEndObject(); break; - case JTokenType.Array: + case JsonContainerType.Array: WriteEndArray(); break; - case JTokenType.Constructor: + case JsonContainerType.Constructor: WriteEndConstructor(); break; default: @@ -502,36 +569,36 @@ namespace Newtonsoft.Json private void AutoCompleteAll() { - while (_top > 0) + while (Top > 0) { WriteEnd(); } } - private JTokenType GetTypeForCloseToken(JsonToken token) + private JsonContainerType GetTypeForCloseToken(JsonToken token) { switch (token) { case JsonToken.EndObject: - return JTokenType.Object; + return JsonContainerType.Object; case JsonToken.EndArray: - return JTokenType.Array; + return JsonContainerType.Array; case JsonToken.EndConstructor: - return JTokenType.Constructor; + return JsonContainerType.Constructor; default: throw new JsonWriterException("No type for token: " + token); } } - private JsonToken GetCloseTokenForType(JTokenType type) + private JsonToken GetCloseTokenForType(JsonContainerType type) { switch (type) { - case JTokenType.Object: + case JsonContainerType.Object: return JsonToken.EndObject; - case JTokenType.Array: + case JsonContainerType.Array: return JsonToken.EndArray; - case JTokenType.Constructor: + case JsonContainerType.Constructor: return JsonToken.EndConstructor; default: throw new JsonWriterException("No close token for type: " + type); @@ -543,15 +610,24 @@ namespace Newtonsoft.Json // write closing symbol and calculate new state int levelsToComplete = 0; + JsonContainerType type = GetTypeForCloseToken(tokenBeingClosed); - for (int i = 0; i < _top; i++) + if (_currentPosition.Type == type) { - int currentLevel = _top - i; - - if (_stack[currentLevel] == GetTypeForCloseToken(tokenBeingClosed)) + levelsToComplete = 1; + } + else + { + int top = Top - 2; + for (int i = top; i >= 0; i--) { - levelsToComplete = i + 1; - break; + int currentLevel = top - i; + + if (_stack[currentLevel].Type == type) + { + levelsToComplete = i + 2; + break; + } } } @@ -569,26 +645,26 @@ namespace Newtonsoft.Json } WriteEnd(token); - } - JTokenType currentLevelType = Peek(); + JsonContainerType currentLevelType = Peek(); - switch (currentLevelType) - { - case JTokenType.Object: - _currentState = State.Object; - break; - case JTokenType.Array: - _currentState = State.Array; - break; - case JTokenType.Constructor: - _currentState = State.Array; - break; - case JTokenType.None: - _currentState = State.Start; - break; - default: - throw new JsonWriterException("Unknown JsonType: " + currentLevelType); + switch (currentLevelType) + { + case JsonContainerType.Object: + _currentState = State.Object; + break; + case JsonContainerType.Array: + _currentState = State.Array; + break; + case JsonContainerType.Constructor: + _currentState = State.Array; + break; + case JsonContainerType.None: + _currentState = State.Start; + break; + default: + throw new JsonWriterException("Unknown JsonType: " + currentLevelType); + } } } @@ -623,11 +699,18 @@ namespace Newtonsoft.Json internal void AutoComplete(JsonToken tokenBeingWritten) { + if (tokenBeingWritten != JsonToken.StartObject + && tokenBeingWritten != JsonToken.StartArray + && tokenBeingWritten != JsonToken.StartConstructor) + UpdateScopeWithFinishedValue(); + // gets new state based on the current state and what is being written State newState = StateArray[(int)tokenBeingWritten][(int)_currentState]; if (newState == State.Error) + { throw new JsonWriterException("Token {0} in state {1} would result in an invalid JSON object.".FormatWith(CultureInfo.InvariantCulture, tokenBeingWritten.ToString(), _currentState.ToString())); + } if ((_currentState == State.Object || _currentState == State.Array || _currentState == State.Constructor) && tokenBeingWritten != JsonToken.Comment) { diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 8256f4f..b1bc331 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -85,7 +85,7 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.0.8.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.0.8.14618")] +[assembly: AssemblyFileVersion("4.0.8.14623")] #endif [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/ErrorContext.cs b/Src/Newtonsoft.Json/Serialization/ErrorContext.cs index 2756d93..28f7ad5 100644 --- a/Src/Newtonsoft.Json/Serialization/ErrorContext.cs +++ b/Src/Newtonsoft.Json/Serialization/ErrorContext.cs @@ -35,11 +35,12 @@ namespace Newtonsoft.Json.Serialization /// public class ErrorContext { - internal ErrorContext(object originalObject, object member, Exception error) + internal ErrorContext(object originalObject, object member, string path, Exception error) { OriginalObject = originalObject; Member = member; Error = error; + Path = path; } /// @@ -58,6 +59,11 @@ namespace Newtonsoft.Json.Serialization /// The member that caused the error. public object Member { get; private set; } /// + /// Gets the path of the JSON location when the error occurred. + /// + /// The path of the JSON location when the error occurred. + public string Path { get; private set; } + /// /// Gets or sets a value indicating whether this is handled. /// /// true if handled; otherwise, false. diff --git a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs index 7fd8b74..3204484 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs @@ -37,7 +37,12 @@ namespace Newtonsoft.Json.Serialization /// public class JsonArrayContract : JsonContract { - internal Type CollectionItemType { get; private set; } + /// + /// Gets the of the collection items. + /// + /// The of the collection items. + public Type CollectionItemType { get; private set; } + internal JsonContract CollectionItemContract { get; set; } private readonly bool _isCollectionItemTypeNullableType; diff --git a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs index 975c937..c3af3f3 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs @@ -42,8 +42,16 @@ namespace Newtonsoft.Json.Serialization /// The property name resolver. public Func PropertyNameResolver { get; set; } - internal Type DictionaryKeyType { get; private set; } - internal Type DictionaryValueType { get; private set; } + /// + /// Gets the of the dictionary keys. + /// + /// The of the dictionary keys. + public Type DictionaryKeyType { get; private set; } + /// + /// Gets the of the dictionary values. + /// + /// The of the dictionary values. + public Type DictionaryValueType { get; private set; } internal JsonContract DictionaryKeyContract { get; set; } internal JsonContract DictionaryValueContract { get; set; } diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs index 761fa76..1c51346 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs @@ -79,10 +79,10 @@ namespace Newtonsoft.Json.Serialization } } - protected ErrorContext GetErrorContext(object currentObject, object member, Exception error) + protected ErrorContext GetErrorContext(object currentObject, object member, string path, Exception error) { if (_currentErrorContext == null) - _currentErrorContext = new ErrorContext(currentObject, member, error); + _currentErrorContext = new ErrorContext(currentObject, member, path, error); if (_currentErrorContext.Error != error) throw new InvalidOperationException("Current error context error is different to requested error."); @@ -98,9 +98,9 @@ namespace Newtonsoft.Json.Serialization _currentErrorContext = null; } - protected bool IsErrorHandled(object currentObject, JsonContract contract, object keyValue, Exception ex) + protected bool IsErrorHandled(object currentObject, JsonContract contract, object keyValue, string path, Exception ex) { - ErrorContext errorContext = GetErrorContext(currentObject, keyValue, ex); + ErrorContext errorContext = GetErrorContext(currentObject, keyValue, path, ex); contract.InvokeOnError(currentObject, Serializer.Context, errorContext); if (!errorContext.Handled) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 2957578..40054cc 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -712,7 +712,7 @@ namespace Newtonsoft.Json.Serialization } catch (Exception ex) { - if (IsErrorHandled(dictionary, contract, keyValue, ex)) + if (IsErrorHandled(dictionary, contract, keyValue, reader.Path, ex)) HandleError(reader, initialDepth); else throw; @@ -767,39 +767,47 @@ namespace Newtonsoft.Json.Serialization contract.InvokeOnDeserializing(list, Serializer.Context); int initialDepth = reader.Depth; + int index = 0; JsonContract collectionItemContract = GetContractSafe(contract.CollectionItemType); JsonConverter collectionItemConverter = GetConverter(collectionItemContract, null); - while (ReadForType(reader, collectionItemContract, collectionItemConverter != null, true)) + while (true) { - switch (reader.TokenType) + try { - case JsonToken.EndArray: - contract.InvokeOnDeserialized(list, Serializer.Context); - - return wrappedList.UnderlyingCollection; - case JsonToken.Comment: - break; - default: - try + if (ReadForType(reader, collectionItemContract, collectionItemConverter != null, true)) + { + switch (reader.TokenType) { - object value = CreateValueNonProperty(reader, contract.CollectionItemType, collectionItemContract, collectionItemConverter); + case JsonToken.EndArray: + contract.InvokeOnDeserialized(list, Serializer.Context); - wrappedList.Add(value); - } - catch (Exception ex) - { - if (IsErrorHandled(list, contract, wrappedList.Count, ex)) - { - HandleError(reader, initialDepth); - } - else - { - throw; - } + return wrappedList.UnderlyingCollection; + case JsonToken.Comment: + break; + default: + object value = CreateValueNonProperty(reader, contract.CollectionItemType, collectionItemContract, collectionItemConverter); + + wrappedList.Add(value); + break; } + } + else + { break; + } + } + catch (Exception ex) + { + if (IsErrorHandled(list, contract, index, reader.Path, ex)) + HandleError(reader, initialDepth); + else + throw; + } + finally + { + index++; } } @@ -911,7 +919,7 @@ namespace Newtonsoft.Json.Serialization } catch (Exception ex) { - if (IsErrorHandled(newObject, contract, memberName, ex)) + if (IsErrorHandled(newObject, contract, memberName, reader.Path, ex)) HandleError(reader, initialDepth); else throw; @@ -1155,71 +1163,88 @@ namespace Newtonsoft.Json.Serialization switch (reader.TokenType) { case JsonToken.PropertyName: - string memberName = reader.Value.ToString(); - - try { - // attempt exact case match first - // then try match ignoring case - JsonProperty property = contract.Properties.GetClosestMatchProperty(memberName); + string memberName = reader.Value.ToString(); - if (property == null) + try { - if (Serializer.MissingMemberHandling == MissingMemberHandling.Error) - throw CreateSerializationException(reader, "Could not find member '{0}' on object of type '{1}'".FormatWith(CultureInfo.InvariantCulture, memberName, contract.UnderlyingType.Name)); + // attempt exact case match first + // then try match ignoring case + JsonProperty property = contract.Properties.GetClosestMatchProperty(memberName); - reader.Skip(); - continue; - } + if (property == null) + { + if (Serializer.MissingMemberHandling == MissingMemberHandling.Error) + throw CreateSerializationException(reader, "Could not find member '{0}' on object of type '{1}'".FormatWith(CultureInfo.InvariantCulture, memberName, contract.UnderlyingType.Name)); - if (property.PropertyContract == null) - property.PropertyContract = GetContractSafe(property.PropertyType); + reader.Skip(); + continue; + } - JsonConverter propertyConverter = GetConverter(property.PropertyContract, property.MemberConverter); + if (property.PropertyContract == null) + property.PropertyContract = GetContractSafe(property.PropertyType); - if (!ReadForType(reader, property.PropertyContract, propertyConverter != null, false)) - throw CreateSerializationException(reader, "Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); + JsonConverter propertyConverter = GetConverter(property.PropertyContract, property.MemberConverter); - SetPropertyPresence(reader, property, propertiesPresence); + if (!ReadForType(reader, property.PropertyContract, propertyConverter != null, false)) + throw CreateSerializationException(reader, "Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); - SetPropertyValue(property, propertyConverter, reader, newObject); - } - catch (Exception ex) - { - if (IsErrorHandled(newObject, contract, memberName, ex)) - HandleError(reader, initialDepth); - else - throw; + SetPropertyPresence(reader, property, propertiesPresence); + + SetPropertyValue(property, propertyConverter, reader, newObject); + } + catch (Exception ex) + { + if (IsErrorHandled(newObject, contract, memberName, reader.Path, ex)) + HandleError(reader, initialDepth); + else + throw; + } } break; case JsonToken.EndObject: - foreach (KeyValuePair propertyPresence in propertiesPresence) { - JsonProperty property = propertyPresence.Key; - PropertyPresence presence = propertyPresence.Value; - - switch (presence) + foreach (KeyValuePair propertyPresence in propertiesPresence) { - case PropertyPresence.None: - if (property.Required == Required.AllowNull || property.Required == Required.Always) - throw CreateSerializationException(reader, "Required property '{0}' not found in JSON.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); - - if (property.PropertyContract == null) - property.PropertyContract = GetContractSafe(property.PropertyType); - - if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Populate) - && property.Writable) - property.ValueProvider.SetValue(newObject, EnsureType(reader, property.DefaultValue, CultureInfo.InvariantCulture, property.PropertyContract, property.PropertyType)); - break; - case PropertyPresence.Null: - if (property.Required == Required.Always) - throw CreateSerializationException(reader, "Required property '{0}' expects a value but got null.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); - break; + JsonProperty property = propertyPresence.Key; + PropertyPresence presence = propertyPresence.Value; + + if (presence == PropertyPresence.None || presence == PropertyPresence.Null) + { + try + { + switch (presence) + { + case PropertyPresence.None: + if (property.Required == Required.AllowNull || property.Required == Required.Always) + throw CreateSerializationException(reader, "Required property '{0}' not found in JSON.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); + + if (property.PropertyContract == null) + property.PropertyContract = GetContractSafe(property.PropertyType); + + if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Populate) + && property.Writable) + property.ValueProvider.SetValue(newObject, EnsureType(reader, property.DefaultValue, CultureInfo.InvariantCulture, property.PropertyContract, property.PropertyType)); + break; + case PropertyPresence.Null: + if (property.Required == Required.Always) + throw CreateSerializationException(reader, "Required property '{0}' expects a value but got null.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); + break; + } + } + catch (Exception ex) + { + if (IsErrorHandled(newObject, contract, property.PropertyName, reader.Path, ex)) + HandleError(reader, initialDepth); + else + throw; + } + } } - } - contract.InvokeOnDeserialized(newObject, Serializer.Context); - return newObject; + contract.InvokeOnDeserialized(newObject, Serializer.Context); + return newObject; + } case JsonToken.Comment: // ignore break; diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index a77f83c..30341ef 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -330,7 +330,7 @@ namespace Newtonsoft.Json.Serialization } catch (Exception ex) { - if (IsErrorHandled(value, contract, property.PropertyName, ex)) + if (IsErrorHandled(value, contract, property.PropertyName, writer.ContainerPath, ex)) HandleError(writer, initialDepth); else throw; @@ -439,7 +439,7 @@ namespace Newtonsoft.Json.Serialization } catch (Exception ex) { - if (IsErrorHandled(values.UnderlyingCollection, contract, index, ex)) + if (IsErrorHandled(values.UnderlyingCollection, contract, index, writer.ContainerPath, ex)) HandleError(writer, initialDepth); else throw; @@ -614,7 +614,7 @@ namespace Newtonsoft.Json.Serialization } catch (Exception ex) { - if (IsErrorHandled(values.UnderlyingDictionary, contract, propertyName, ex)) + if (IsErrorHandled(values.UnderlyingDictionary, contract, propertyName, writer.ContainerPath, ex)) HandleError(writer, initialDepth); else throw; -- cgit v1.2.3