diff --git a/Src/Newtonsoft.Json.TestConsole/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.TestConsole/Properties/AssemblyInfo.cs index 9be3690c..aa32efb8 100644 --- a/Src/Newtonsoft.Json.TestConsole/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.TestConsole/Properties/AssemblyInfo.cs @@ -33,4 +33,4 @@ using System.Runtime.InteropServices; // by using the '*' as shown below: // [assembly: AssemblyVersion("6.0.0.0")] [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.6.17905")] +[assembly: AssemblyFileVersion("6.0.6.17911")] diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index be30b977..b22f60d7 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -74,4 +74,4 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.6.17905")] +[assembly: AssemblyFileVersion("6.0.6.17911")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 8da564ea..f3ba31f1 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -699,6 +699,122 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(ratio.Denominator, ratio2.Denominator); Assert.AreEqual(ratio.Numerator, ratio2.Numerator); } + + public class PreserveReferencesCallbackTestObject : ISerializable + { + internal string _stringValue; + internal int _intValue; + internal PersonReference _person1; + internal PersonReference _person2; + internal PersonReference _person3; + internal PreserveReferencesCallbackTestObject _parent; + internal SerializationInfo _serializationInfo; + + public PreserveReferencesCallbackTestObject(string stringValue, int intValue, PersonReference p1, PersonReference p2, PersonReference p3) + { + _stringValue = stringValue; + _intValue = intValue; + _person1 = p1; + _person2 = p2; + _person3 = p3; + } + + protected PreserveReferencesCallbackTestObject(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)); + info.AddValue("parent", _parent, typeof(PreserveReferencesCallbackTestObject)); + } + + [OnDeserialized] + private void OnDeserializedMethod(StreamingContext context) + { + 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)); + _parent = (PreserveReferencesCallbackTestObject)_serializationInfo.GetValue("parent", typeof(PreserveReferencesCallbackTestObject)); + + _serializationInfo = null; + } + } + + [Test] + public void PreserveReferencesCallbackTest() + { + var p1 = new PersonReference + { + Name = "John Smith" + }; + var p2 = new PersonReference + { + Name = "Mary Sue", + }; + + p1.Spouse = p2; + p2.Spouse = p1; + + var obj = new PreserveReferencesCallbackTestObject("string!", 42, p1, p2, p1); + obj._parent = obj; + + 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"" + }, + ""parent"": { + ""$ref"": ""1"" + } +}"); + + PreserveReferencesCallbackTestObject 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); + Assert.AreEqual(obj2._person2.Spouse, obj2._person1); + Assert.AreEqual(obj2._parent, obj2); + } #endif [Test] @@ -3935,9 +4051,9 @@ Path '', line 1, position 1."); JsonTypeReflector.SetFullyTrusted(false); JsonConvert.DeserializeObject("{booleanValue:true}"); - }, @"Type 'Newtonsoft.Json.Tests.Serialization.JsonSerializerTest+ISerializableTestObject' implements ISerializable but cannot be deserialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data. -To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true. -Path 'booleanValue', line 1, position 14."); + }, @"Type 'Newtonsoft.Json.Tests.Serialization.JsonSerializerTest+ISerializableTestObject' implements ISerializable but cannot be deserialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data." + Environment.NewLine + + @"To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true." + Environment.NewLine + + @"Path 'booleanValue', line 1, position 14."); } finally { @@ -3956,8 +4072,9 @@ Path 'booleanValue', line 1, position 14."); ISerializableTestObject value = new ISerializableTestObject("string!", 0, default(DateTimeOffset), null); JsonConvert.SerializeObject(value); - }, @"Type 'Newtonsoft.Json.Tests.Serialization.JsonSerializerTest+ISerializableTestObject' implements ISerializable but cannot be serialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data. -To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true. Path ''."); + }, @"Type 'Newtonsoft.Json.Tests.Serialization.JsonSerializerTest+ISerializableTestObject' implements ISerializable but cannot be serialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data." + Environment.NewLine + + @"To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true." + Environment.NewLine + + @"Path ''."); } finally { diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index a857c14b..ae3623f9 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -91,5 +91,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.6.17905")] +[assembly: AssemblyFileVersion("6.0.6.17911")] [assembly: CLSCompliant(true)] 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..d69e5fb5 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 { @@ -50,5 +51,4 @@ namespace Newtonsoft.Json.Serialization } } } - #endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 73fe9718..e90c7e4d 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; @@ -1518,6 +1495,23 @@ namespace Newtonsoft.Json.Serialization 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 97e8dac3..7c0013d6 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -752,7 +752,12 @@ namespace Newtonsoft.Json.Serialization { 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);