Support reference preservation for ISerializable objects.

This commit is contained in:
Inverness
2014-11-09 17:31:10 -06:00
parent 7b4995523b
commit 45d5e90da1
5 changed files with 157 additions and 31 deletions
@@ -699,6 +699,112 @@ namespace Newtonsoft.Json.Tests.Serialization
Assert.AreEqual(ratio.Denominator, ratio2.Denominator);
Assert.AreEqual(ratio.Numerator, ratio2.Numerator);
}
public class IDeserializationCallbackTestObject : ISerializable, IDeserializationCallback
{
internal string _stringValue;
internal int _intValue;
internal PersonReference _person1;
internal PersonReference _person2;
internal PersonReference _person3;
internal SerializationInfo _serializationInfo;
public IDeserializationCallbackTestObject(string stringValue, int intValue, PersonReference p1, PersonReference p2, PersonReference p3)
{
_stringValue = stringValue;
_intValue = intValue;
_person1 = p1;
_person2 = p2;
_person3 = p3;
}
protected IDeserializationCallbackTestObject(SerializationInfo info, StreamingContext context)
{
_serializationInfo = info;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("stringValue", _stringValue);
info.AddValue("intValue", _intValue);
info.AddValue("person1", _person1, typeof(PersonReference));
info.AddValue("person2", _person2, typeof(PersonReference));
info.AddValue("person3", _person3, typeof(PersonReference));
}
void IDeserializationCallback.OnDeserialization(object sender)
{
if (_serializationInfo == null)
return;
_stringValue = _serializationInfo.GetString("stringValue");
_intValue = _serializationInfo.GetInt32("intValue");
_person1 = (PersonReference) _serializationInfo.GetValue("person1", typeof(PersonReference));
_person2 = (PersonReference) _serializationInfo.GetValue("person2", typeof(PersonReference));
_person3 = (PersonReference) _serializationInfo.GetValue("person3", typeof(PersonReference));
_serializationInfo = null;
}
}
[Test]
public void DeserializeIDeserializationCallback()
{
var p1 = new PersonReference
{
Name = "John Smith"
};
var p2 = new PersonReference
{
Name = "Mary Sue",
};
p1.Spouse = p2;
p2.Spouse = p1;
var obj = new IDeserializationCallbackTestObject("string!", 42, p1, p2, p1);
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.All,
Formatting = Formatting.Indented
};
string json = JsonConvert.SerializeObject(obj, settings);
StringAssert.AreEqual(json, @"{
""$id"": ""1"",
""stringValue"": ""string!"",
""intValue"": 42,
""person1"": {
""$id"": ""2"",
""Name"": ""John Smith"",
""Spouse"": {
""$id"": ""3"",
""Name"": ""Mary Sue"",
""Spouse"": {
""$ref"": ""2""
}
}
},
""person2"": {
""$ref"": ""3""
},
""person3"": {
""$ref"": ""2""
}
}");
IDeserializationCallbackTestObject obj2 = JsonConvert.DeserializeObject<IDeserializationCallbackTestObject>(json);
Assert.AreEqual(obj._stringValue, obj2._stringValue);
Assert.AreEqual(obj._intValue, obj2._intValue);
Assert.AreEqual(obj._person1.Name, obj2._person1.Name);
Assert.AreEqual(obj._person2.Name, obj2._person2.Name);
Assert.AreEqual(obj._person3.Name, obj2._person3.Name);
Assert.AreEqual(obj2._person1, obj2._person3);
Assert.AreEqual(obj2._person1.Spouse, obj2._person2);
}
#endif
[Test]
@@ -34,13 +34,18 @@ namespace Newtonsoft.Json.Serialization
{
internal class JsonFormatterConverter : IFormatterConverter
{
private readonly JsonSerializer _serializer;
private readonly JsonSerializerInternalReader _reader;
private readonly JsonISerializableContract _contract;
private readonly JsonProperty _member;
public JsonFormatterConverter(JsonSerializer serializer)
public JsonFormatterConverter(JsonSerializerInternalReader reader, JsonISerializableContract contract, JsonProperty member)
{
ValidationUtils.ArgumentNotNull(serializer, "serializer");
ValidationUtils.ArgumentNotNull(reader, "serializer");
ValidationUtils.ArgumentNotNull(contract, "contract");
_serializer = serializer;
_reader = reader;
_contract = contract;
_member = member;
}
private T GetTokenValue<T>(object value)
@@ -59,7 +64,7 @@ namespace Newtonsoft.Json.Serialization
if (token == null)
throw new ArgumentException("Value is not a JToken.", "value");
return _serializer.Deserialize(token.CreateReader(), type);
return _reader.CreateISerializableItem(token, type, _contract, _member);
}
public object Convert(object value, TypeCode typeCode)
@@ -25,6 +25,7 @@
#if !(NETFX_CORE || PORTABLE || PORTABLE40)
using System;
using System.Runtime.Serialization;
namespace Newtonsoft.Json.Serialization
{
@@ -39,6 +40,12 @@ namespace Newtonsoft.Json.Serialization
/// <value>The ISerializable object constructor.</value>
public ObjectConstructor<object> ISerializableCreator { get; set; }
/// <summary>
/// Gets whether the type implements IDeserializationCallback.
/// </summary>
/// <value>True if the type implements IDeserialiationCallback, otherwise false.</value>
public bool HasDeserializationCallback { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="JsonISerializableContract"/> class.
/// </summary>
@@ -47,6 +54,7 @@ namespace Newtonsoft.Json.Serialization
: base(underlyingType)
{
ContractType = JsonContractType.Serializable;
HasDeserializationCallback = typeof(IDeserializationCallback).IsAssignableFrom(underlyingType);
}
}
}
@@ -58,9 +58,6 @@ namespace Newtonsoft.Json.Serialization
}
private JsonSerializerProxy _internalSerializer;
#if !(NETFX_CORE || PORTABLE40 || PORTABLE)
private JsonFormatterConverter _formatterConverter;
#endif
public JsonSerializerInternalReader(JsonSerializer serializer)
: base(serializer)
@@ -193,16 +190,6 @@ namespace Newtonsoft.Json.Serialization
return _internalSerializer;
}
#if !(NETFX_CORE || PORTABLE40 || PORTABLE)
private JsonFormatterConverter GetFormatterConverter()
{
if (_formatterConverter == null)
_formatterConverter = new JsonFormatterConverter(GetInternalSerializer());
return _formatterConverter;
}
#endif
private JToken CreateJToken(JsonReader reader, JsonContract contract)
{
ValidationUtils.ArgumentNotNull(reader, "reader");
@@ -1468,7 +1455,7 @@ namespace Newtonsoft.Json.Serialization
if (TraceWriter != null && TraceWriter.LevelFilter >= TraceLevel.Info)
TraceWriter.Trace(TraceLevel.Info, JsonPosition.FormatMessage(reader as IJsonLineInfo, reader.Path, "Deserializing {0} using ISerializable constructor.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)), null);
SerializationInfo serializationInfo = new SerializationInfo(contract.UnderlyingType, GetFormatterConverter());
SerializationInfo serializationInfo = new SerializationInfo(contract.UnderlyingType, new JsonFormatterConverter(this, contract, member));
bool finished = false;
do
@@ -1479,17 +1466,7 @@ namespace Newtonsoft.Json.Serialization
string memberName = reader.Value.ToString();
if (!reader.Read())
throw JsonSerializationException.Create(reader, "Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName));
if (reader.TokenType == JsonToken.StartObject)
{
// this will read any potential type names embedded in json
object o = CreateObject(reader, null, null, null, contract, member, null);
serializationInfo.AddValue(memberName, o);
}
else
{
serializationInfo.AddValue(memberName, JToken.ReadFrom(reader));
}
serializationInfo.AddValue(memberName, JToken.ReadFrom(reader));
break;
case JsonToken.Comment:
break;
@@ -1516,8 +1493,33 @@ namespace Newtonsoft.Json.Serialization
OnDeserializing(reader, contract, createdObject);
OnDeserialized(reader, contract, createdObject);
// According to documentation this method is called after the entire object graph is deserialized,
// however doing this isn't compatible with the type of serialization model used by JSON.
// The creation of an object with $id would end up postponed, causing references to it in the object graph
// to fail. The .NET Framework shows that it's valid to call this on-demand before that point if another
// object depends on this one, which is what will be done here in every case.
if (contract.HasDeserializationCallback)
((IDeserializationCallback) createdObject).OnDeserialization(null);
return createdObject;
}
internal object CreateISerializableItem(JToken token, Type type, JsonISerializableContract contract, JsonProperty member)
{
JsonContract itemContract = GetContractSafe(type);
JsonConverter itemConverter = GetConverter(itemContract, null, contract, member);
JsonReader tokenReader = token.CreateReader();
CheckedRead(tokenReader); // Move to first token
object result;
if (itemConverter != null && itemConverter.CanRead)
result = DeserializeConvertable(itemConverter, tokenReader, type, null);
else
result = CreateValueInternal(tokenReader, type, itemContract, null, contract, member, null);
return result;
}
#endif
#if !(NET35 || NET20 || PORTABLE40)
@@ -749,7 +749,12 @@ To fix this error either change the environment to be fully trusted, change the
{
JsonContract valueContract = GetContractSafe(serializationEntry.Value);
if (CheckForCircularReference(writer, serializationEntry.Value, null, valueContract, contract, member))
if (ShouldWriteReference(serializationEntry.Value, null, valueContract, contract, member))
{
writer.WritePropertyName(serializationEntry.Name);
WriteReference(writer, serializationEntry.Value);
}
else if (CheckForCircularReference(writer, serializationEntry.Value, null, valueContract, contract, member))
{
writer.WritePropertyName(serializationEntry.Name);
SerializeValue(writer, serializationEntry.Value, valueContract, null, contract, member);