Skip to content

Commit

Permalink
Support reference preservation for ISerializable objects.
Browse files Browse the repository at this point in the history
  • Loading branch information
Inverness committed Nov 9, 2014
1 parent 7b49955 commit 45d5e90
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 31 deletions.
106 changes: 106 additions & 0 deletions Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -699,6 +699,112 @@ public void DeserializeISerializableIConvertible()
Assert.AreEqual(ratio.Denominator, ratio2.Denominator); Assert.AreEqual(ratio.Denominator, ratio2.Denominator);
Assert.AreEqual(ratio.Numerator, ratio2.Numerator); 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 #endif


[Test] [Test]
Expand Down
15 changes: 10 additions & 5 deletions Src/Newtonsoft.Json/Serialization/JsonFormatterConverter.cs
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -34,13 +34,18 @@ namespace Newtonsoft.Json.Serialization
{ {
internal class JsonFormatterConverter : IFormatterConverter 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) private T GetTokenValue<T>(object value)
Expand All @@ -59,7 +64,7 @@ public object Convert(object value, Type type)
if (token == null) if (token == null)
throw new ArgumentException("Value is not a JToken.", "value"); 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) public object Convert(object value, TypeCode typeCode)
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@


#if !(NETFX_CORE || PORTABLE || PORTABLE40) #if !(NETFX_CORE || PORTABLE || PORTABLE40)
using System; using System;
using System.Runtime.Serialization;


namespace Newtonsoft.Json.Serialization namespace Newtonsoft.Json.Serialization
{ {
Expand All @@ -39,6 +40,12 @@ public class JsonISerializableContract : JsonContainerContract
/// <value>The ISerializable object constructor.</value> /// <value>The ISerializable object constructor.</value>
public ObjectConstructor<object> ISerializableCreator { get; set; } 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> /// <summary>
/// Initializes a new instance of the <see cref="JsonISerializableContract"/> class. /// Initializes a new instance of the <see cref="JsonISerializableContract"/> class.
/// </summary> /// </summary>
Expand All @@ -47,6 +54,7 @@ public JsonISerializableContract(Type underlyingType)
: base(underlyingType) : base(underlyingType)
{ {
ContractType = JsonContractType.Serializable; ContractType = JsonContractType.Serializable;
HasDeserializationCallback = typeof(IDeserializationCallback).IsAssignableFrom(underlyingType);
} }
} }
} }
Expand Down
52 changes: 27 additions & 25 deletions Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -58,9 +58,6 @@ internal enum PropertyPresence
} }


private JsonSerializerProxy _internalSerializer; private JsonSerializerProxy _internalSerializer;
#if !(NETFX_CORE || PORTABLE40 || PORTABLE)
private JsonFormatterConverter _formatterConverter;
#endif


public JsonSerializerInternalReader(JsonSerializer serializer) public JsonSerializerInternalReader(JsonSerializer serializer)
: base(serializer) : base(serializer)
Expand Down Expand Up @@ -193,16 +190,6 @@ private JsonSerializerProxy GetInternalSerializer()
return _internalSerializer; 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) private JToken CreateJToken(JsonReader reader, JsonContract contract)
{ {
ValidationUtils.ArgumentNotNull(reader, "reader"); ValidationUtils.ArgumentNotNull(reader, "reader");
Expand Down Expand Up @@ -1468,7 +1455,7 @@ private object CreateISerializable(JsonReader reader, JsonISerializableContract
if (TraceWriter != null && TraceWriter.LevelFilter >= TraceLevel.Info) 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); 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; bool finished = false;
do do
Expand All @@ -1479,17 +1466,7 @@ private object CreateISerializable(JsonReader reader, JsonISerializableContract
string memberName = reader.Value.ToString(); string memberName = reader.Value.ToString();
if (!reader.Read()) if (!reader.Read())
throw JsonSerializationException.Create(reader, "Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); throw JsonSerializationException.Create(reader, "Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName));

serializationInfo.AddValue(memberName, JToken.ReadFrom(reader));
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));
}
break; break;
case JsonToken.Comment: case JsonToken.Comment:
break; break;
Expand All @@ -1516,8 +1493,33 @@ private object CreateISerializable(JsonReader reader, JsonISerializableContract
OnDeserializing(reader, contract, createdObject); OnDeserializing(reader, contract, createdObject);
OnDeserialized(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; 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 #endif


#if !(NET35 || NET20 || PORTABLE40) #if !(NET35 || NET20 || PORTABLE40)
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -749,7 +749,12 @@ private void SerializeISerializable(JsonWriter writer, ISerializable value, Json
{ {
JsonContract valueContract = GetContractSafe(serializationEntry.Value); 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); writer.WritePropertyName(serializationEntry.Name);
SerializeValue(writer, serializationEntry.Value, valueContract, null, contract, member); SerializeValue(writer, serializationEntry.Value, valueContract, null, contract, member);
Expand Down

0 comments on commit 45d5e90

Please sign in to comment.