Support reference preservation for ISerializable objects.
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user