-Improved error handling to return incomplete values at end of JSON content

This commit is contained in:
James Newton-King
2012-08-04 00:58:10 +12:00
parent be82ab9f0b
commit d18e7d6490
7 changed files with 236 additions and 110 deletions
@@ -72,5 +72,5 @@ using System.Security;
// by using the '*' as shown below:
[assembly: AssemblyVersion("4.5.0.0")]
#if !PocketPC
[assembly: AssemblyFileVersion("4.5.8.15201")]
[assembly: AssemblyFileVersion("4.5.8.15204")]
#endif
@@ -181,7 +181,7 @@ namespace Newtonsoft.Json.Tests.Serialization
[Test]
public void DynamicCamelCasePropertyNames()
{
dynamic o = new DynamicTests.TestDynamicObject();
dynamic o = new TestDynamicObject();
o.Text = "Text!";
o.Integer = int.MaxValue;
@@ -50,77 +50,6 @@ namespace Newtonsoft.Json.Tests.Serialization
[TestFixture]
public class DynamicTests : TestFixtureBase
{
public class DynamicChildObject
{
public string Text { get; set; }
public int Integer { get; set; }
}
public class TestDynamicObject : DynamicObject
{
private readonly Dictionary<string, object> _members;
public int Int;
[JsonProperty]
public bool Explicit;
public DynamicChildObject ChildObject { get; set; }
internal Dictionary<string, object> Members
{
get { return _members; }
}
public TestDynamicObject()
{
_members = new Dictionary<string, object>();
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return _members.Keys.Union(new[] { "Int", "ChildObject" });
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
Type targetType = binder.Type;
if (targetType == typeof(IDictionary<string, object>) ||
targetType == typeof(IDictionary))
{
result = new Dictionary<string, object>(_members);
return true;
}
else
{
return base.TryConvert(binder, out result);
}
}
public override bool TryDeleteMember(DeleteMemberBinder binder)
{
return _members.Remove(binder.Name);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return _members.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_members[binder.Name] = value;
return true;
}
}
public class ErrorSettingDynamicObject : DynamicObject
{
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return false;
}
}
[Test]
public void SerializeDynamicObject()
{
@@ -317,5 +246,76 @@ namespace Newtonsoft.Json.Tests.Serialization
Assert.AreEqual(false, foo.Values["retweeted"]);
}
}
public class DynamicChildObject
{
public string Text { get; set; }
public int Integer { get; set; }
}
public class TestDynamicObject : DynamicObject
{
private readonly Dictionary<string, object> _members;
public int Int;
[JsonProperty]
public bool Explicit;
public DynamicChildObject ChildObject { get; set; }
internal Dictionary<string, object> Members
{
get { return _members; }
}
public TestDynamicObject()
{
_members = new Dictionary<string, object>();
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return _members.Keys.Union(new[] { "Int", "ChildObject" });
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
Type targetType = binder.Type;
if (targetType == typeof(IDictionary<string, object>) ||
targetType == typeof(IDictionary))
{
result = new Dictionary<string, object>(_members);
return true;
}
else
{
return base.TryConvert(binder, out result);
}
}
public override bool TryDeleteMember(DeleteMemberBinder binder)
{
return _members.Remove(binder.Name);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return _members.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_members[binder.Name] = value;
return true;
}
}
public class ErrorSettingDynamicObject : DynamicObject
{
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return false;
}
}
}
#endif
@@ -6447,6 +6447,26 @@ Parameter name: value",
}
#endif
[Test]
public void AdditionalContentAfterFinish()
{
ExceptionAssert.Throws<JsonException>(
"Additional text found in JSON string after finishing deserializing object.",
() =>
{
string json = "[{},1]";
JsonSerializer serializer = new JsonSerializer();
serializer.CheckAdditionalContent = true;
var reader = new JsonTextReader(new StringReader(json));
reader.Read();
reader.Read();
serializer.Deserialize(reader, typeof (MyType));
});
}
[Test]
public void DeserializeRelativeUri()
{
@@ -552,7 +552,7 @@ namespace Newtonsoft.Json.Tests.Serialization
{
IList<string> errors = new List<string>();
const string input = "{\"events\":[{\"code\":64411,\"prio";
const string input = "{\"events\":[{\"code\":64411},{\"code\":64411,\"prio";
const int maxDepth = 256;
using (var jsonTextReader = new JsonTextReader(new StringReader(input)) {MaxDepth = maxDepth})
@@ -563,14 +563,84 @@ namespace Newtonsoft.Json.Tests.Serialization
errors.Add(e.ErrorContext.Error.Message);
e.ErrorContext.Handled = true;
};
jsonSerializer.Deserialize(jsonTextReader, typeof (LogMessage));
LogMessage logMessage = jsonSerializer.Deserialize<LogMessage>(jsonTextReader);
Assert.IsNotNull(logMessage.Events);
Assert.AreEqual(1, logMessage.Events.Count);
Assert.AreEqual("64411", logMessage.Events[0].Code);
}
Assert.AreEqual(3, errors.Count);
Assert.AreEqual(@"Unterminated string. Expected delimiter: "". Path 'events[1].code', line 1, position 45.", errors[0]);
Assert.AreEqual(@"Unexpected end when deserializing array. Path 'events[1].code', line 1, position 45.", errors[1]);
Assert.AreEqual(@"Unexpected end when deserializing object. Path 'events[1].code', line 1, position 45.", errors[2]);
}
[Test]
public void ErrorHandlingEndOfContentDictionary()
{
IList<string> errors = new List<string>();
const string input = "{\"events\":{\"code\":64411},\"events2\":{\"code\":64412,";
const int maxDepth = 256;
using (var jsonTextReader = new JsonTextReader(new StringReader(input)) {MaxDepth = maxDepth})
{
JsonSerializer jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings {MaxDepth = maxDepth});
jsonSerializer.Error += (sender, e) =>
{
errors.Add(e.ErrorContext.Error.Message);
e.ErrorContext.Handled = true;
};
IDictionary<string, LogEvent> logEvents = jsonSerializer.Deserialize<IDictionary<string, LogEvent>>(jsonTextReader);
Assert.IsNotNull(logEvents);
Assert.AreEqual(2, logEvents.Count);
Assert.AreEqual("64411", logEvents["events"].Code);
Assert.AreEqual("64412", logEvents["events2"].Code);
}
Assert.AreEqual(2, errors.Count);
Assert.AreEqual(@"Unterminated string. Expected delimiter: "". Path 'events[0].code', line 1, position 30.", errors[0]);
Assert.AreEqual(@"Unexpected end when deserializing object. Path 'events[0].code', line 1, position 30.", errors[1]);
Assert.AreEqual(@"Unexpected end when deserializing object. Path 'events2.code', line 1, position 49.", errors[0]);
Assert.AreEqual(@"Unexpected end when deserializing object. Path 'events2.code', line 1, position 49.", errors[1]);
}
#if !(NET35 || NET20 || WINDOWS_PHONE || PORTABLE)
[Test]
public void ErrorHandlingEndOfContentDynamic()
{
IList<string> errors = new List<string>();
string json = @"{
""Explicit"": true,
""Decimal"": 99.9,
""Int"": 1,
""ChildObject"": {
""Integer"": 123";
TestDynamicObject newDynamicObject = JsonConvert.DeserializeObject<TestDynamicObject>(json, new JsonSerializerSettings
{
Error = (sender, e) =>
{
errors.Add(e.ErrorContext.Error.Message);
e.ErrorContext.Handled = true;
}
});
Assert.AreEqual(true, newDynamicObject.Explicit);
dynamic d = newDynamicObject;
Assert.AreEqual(99.9, d.Decimal);
Assert.AreEqual(1, d.Int);
Assert.AreEqual(123, d.ChildObject.Integer);
Assert.AreEqual(2, errors.Count);
Assert.AreEqual(@"Unexpected end when deserializing object. Path 'ChildObject.Integer', line 6, position 19.", errors[0]);
Assert.AreEqual(@"Unexpected end when deserializing object. Path 'ChildObject.Integer', line 6, position 19.", errors[1]);
}
#endif
}
public class ThrowingReader : TextReader
@@ -639,6 +709,7 @@ namespace Newtonsoft.Json.Tests.Serialization
public class LogMessage
{
public string DeviceId { get; set; }
public IList<LogEvent> Events { get; set; }
}
public class LogEvent
@@ -91,7 +91,7 @@ using System.Security;
// by using the '*' as shown below:
[assembly: AssemblyVersion("4.5.0.0")]
#if !PocketPC
[assembly: AssemblyFileVersion("4.5.8.15201")]
[assembly: AssemblyFileVersion("4.5.8.15204")]
#endif
[assembly: CLSCompliant(true)]
@@ -813,6 +813,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
JsonConverter dictionaryValueConverter = contract.ItemConverter ?? GetConverter(contract.ItemContract, null, contract, containerProperty);
bool finished = false;
do
{
switch (reader.TokenType)
@@ -852,15 +853,18 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
case JsonToken.Comment:
break;
case JsonToken.EndObject:
contract.InvokeOnDeserialized(dictionary, Serializer.Context);
return dictionary;
finished = true;
break;
default:
throw JsonSerializationException.Create(reader, "Unexpected token when deserializing object: " + reader.TokenType);
}
} while (reader.Read());
} while (!finished && reader.Read());
throw JsonSerializationException.Create(reader, "Unexpected end when deserializing object.");
if (!finished)
ThrowUnexpectedEndException(reader, contract, dictionary, "Unexpected end when deserializing object.");
contract.InvokeOnDeserialized(dictionary, Serializer.Context);
return dictionary;
}
private object PopulateMultidimensionalArray(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, string id)
@@ -880,7 +884,8 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
listStack.Push(list);
IList currentList = list;
while (true)
bool finished = false;
do
{
int initialDepth = reader.Depth;
@@ -962,9 +967,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
}
else
{
contract.InvokeOnDeserialized(list, Serializer.Context);
return list;
finished = true;
}
break;
case JsonToken.Comment:
@@ -978,9 +981,28 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
break;
}
}
}
} while (!finished);
throw JsonSerializationException.Create(reader, "Unexpected end when deserializing array.");
if (!finished)
ThrowUnexpectedEndException(reader, contract, list, "Unexpected end when deserializing array.");
contract.InvokeOnDeserialized(list, Serializer.Context);
return list;
}
private void ThrowUnexpectedEndException(JsonReader reader, JsonContract contract, object currentObject, string message)
{
try
{
throw JsonSerializationException.Create(reader, message);
}
catch (Exception ex)
{
if (IsErrorHandled(currentObject, contract, null, reader.Path, ex))
HandleError(reader, false, 0);
else
throw;
}
}
private object PopulateList(IWrappedCollection wrappedList, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, string id)
@@ -1006,7 +1028,8 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
int? previousErrorIndex = null;
while (true)
bool finished = false;
do
{
try
{
@@ -1015,9 +1038,8 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
switch (reader.TokenType)
{
case JsonToken.EndArray:
contract.InvokeOnDeserialized(list, Serializer.Context);
return list;
finished = true;
break;
case JsonToken.Comment:
break;
default:
@@ -1061,9 +1083,13 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
throw;
}
}
}
} while (!finished);
throw JsonSerializationException.Create(reader, "Unexpected end when deserializing array.");
if (!finished)
ThrowUnexpectedEndException(reader, contract, list, "Unexpected end when deserializing array.");
contract.InvokeOnDeserialized(list, Serializer.Context);
return list;
}
#if !(SILVERLIGHT || NETFX_CORE || PORTABLE)
@@ -1080,7 +1106,7 @@ To fix this error either change the environment to be fully trusted, change the
SerializationInfo serializationInfo = new SerializationInfo(contract.UnderlyingType, GetFormatterConverter());
bool exit = false;
bool finished = false;
do
{
switch (reader.TokenType)
@@ -1095,12 +1121,15 @@ To fix this error either change the environment to be fully trusted, change the
case JsonToken.Comment:
break;
case JsonToken.EndObject:
exit = true;
finished = true;
break;
default:
throw JsonSerializationException.Create(reader, "Unexpected token when deserializing object: " + reader.TokenType);
}
} while (!exit && reader.Read());
} while (!finished && reader.Read());
if (!finished)
ThrowUnexpectedEndException(reader, contract, serializationInfo, "Unexpected end when deserializing object.");
if (contract.ISerializableCreator == null)
throw JsonSerializationException.Create(reader, "ISerializable type '{0}' does not have a valid constructor. To correctly implement ISerializable a constructor that takes SerializationInfo and StreamingContext parameters should be present.".FormatWith(CultureInfo.InvariantCulture, objectType));
@@ -1139,7 +1168,7 @@ To fix this error either change the environment to be fully trusted, change the
int initialDepth = reader.Depth;
bool exit = false;
bool finished = false;
do
{
switch (reader.TokenType)
@@ -1189,12 +1218,15 @@ To fix this error either change the environment to be fully trusted, change the
}
break;
case JsonToken.EndObject:
exit = true;
finished = true;
break;
default:
throw JsonSerializationException.Create(reader, "Unexpected token when deserializing object: " + reader.TokenType);
}
} while (!exit && reader.Read());
} while (!finished && reader.Read());
if (!finished)
ThrowUnexpectedEndException(reader, contract, newObject, "Unexpected end when deserializing object.");
contract.InvokeOnDeserialized(newObject, Serializer.Context);
@@ -1450,6 +1482,7 @@ To fix this error either change the environment to be fully trusted, change the
int initialDepth = reader.Depth;
bool finished = false;
do
{
switch (reader.TokenType)
@@ -1495,21 +1528,23 @@ To fix this error either change the environment to be fully trusted, change the
}
break;
case JsonToken.EndObject:
{
EndObject(newObject, reader, contract, initialDepth, propertiesPresence);
contract.InvokeOnDeserialized(newObject, Serializer.Context);
return newObject;
}
finished = true;
break;
case JsonToken.Comment:
// ignore
break;
default:
throw JsonSerializationException.Create(reader, "Unexpected token when deserializing object: " + reader.TokenType);
}
} while (reader.Read());
} while (!finished && reader.Read());
throw JsonSerializationException.Create(reader, "Unexpected end when deserializing object.");
if (!finished)
ThrowUnexpectedEndException(reader, contract, newObject, "Unexpected end when deserializing object.");
EndObject(newObject, reader, contract, initialDepth, propertiesPresence);
contract.InvokeOnDeserialized(newObject, Serializer.Context);
return newObject;
}
private void EndObject(object newObject, JsonReader reader, JsonObjectContract contract, int initialDepth, Dictionary<JsonProperty, PropertyPresence> propertiesPresence)