diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 8da564ea..afcc9cff 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -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(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] diff --git a/Src/Newtonsoft.Json/Serialization/JsonFormatterConverter.cs b/Src/Newtonsoft.Json/Serialization/JsonFormatterConverter.cs index b4135d8a..cc68e9d0 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonFormatterConverter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonFormatterConverter.cs @@ -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(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) diff --git a/Src/Newtonsoft.Json/Serialization/JsonISerializableContract.cs b/Src/Newtonsoft.Json/Serialization/JsonISerializableContract.cs index 50d0d4e6..dab49ea4 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonISerializableContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonISerializableContract.cs @@ -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 /// The ISerializable object constructor. public ObjectConstructor ISerializableCreator { get; set; } + /// + /// Gets whether the type implements IDeserializationCallback. + /// + /// True if the type implements IDeserialiationCallback, otherwise false. + public bool HasDeserializationCallback { get; private set; } + /// /// Initializes a new instance of the class. /// @@ -47,6 +54,7 @@ namespace Newtonsoft.Json.Serialization : base(underlyingType) { ContractType = JsonContractType.Serializable; + HasDeserializationCallback = typeof(IDeserializationCallback).IsAssignableFrom(underlyingType); } } } diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 73fe9718..f4b78e0f 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -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) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index 84c5358f..24a65a7d 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -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);