From c2800930ab4a66710c56d5aa6c3fb32269af0446 Mon Sep 17 00:00:00 2001 From: JamesNK Date: Sat, 3 Jul 2010 09:53:57 +0000 Subject: [PATCH] -Added DateTimeKindHandling to BsonWriter to control how a DateTime is converted prior to being serialized -Added Required.Always attribute validation when writing JSON as well reading -Added TypeNameHandling.Auto to automatically write the type name when a value doesn't match the declared type -Fixed reading multi-byte strings in BSON --- .../Bson/BsonWriterTests.cs | 40 +++++++ Src/Newtonsoft.Json.Tests/PerformanceTests.cs | 8 +- .../Serialization/JsonSerializerTest.cs | 20 +++- .../Serialization/TypeNameHandlingTests.cs | 108 +++++++++++------- .../TestObjects/HolderClass.cs | 2 +- Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs | 11 +- Src/Newtonsoft.Json/Bson/BsonReader.cs | 14 ++- Src/Newtonsoft.Json/Bson/BsonWriter.cs | 11 ++ Src/Newtonsoft.Json/JsonConvert.cs | 9 +- Src/Newtonsoft.Json/JsonSerializer.cs | 2 +- .../JsonSerializerInternalWriter.cs | 99 +++++++++++----- Src/Newtonsoft.Json/TypeNameHandling.cs | 4 + 12 files changed, 237 insertions(+), 91 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Bson/BsonWriterTests.cs b/Src/Newtonsoft.Json.Tests/Bson/BsonWriterTests.cs index ca641326..b90e74a2 100644 --- a/Src/Newtonsoft.Json.Tests/Bson/BsonWriterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Bson/BsonWriterTests.cs @@ -558,5 +558,45 @@ namespace Newtonsoft.Json.Tests.Bson Assert.IsFalse(reader.Read()); } + + [Test] + public void WriteDateTimes() + { + MemoryStream ms = new MemoryStream(); + BsonWriter writer = new BsonWriter(ms); + writer.DateTimeKindHandling = DateTimeKind.Unspecified; + + writer.WriteStartArray(); + writer.WriteValue(new DateTime(2000, 10, 12, 20, 55, 0, DateTimeKind.Utc)); + writer.WriteValue(new DateTime(2000, 10, 12, 20, 55, 0, DateTimeKind.Local)); + writer.WriteValue(new DateTime(2000, 10, 12, 20, 55, 0, DateTimeKind.Unspecified)); + writer.WriteEndArray(); + + ms.Seek(0, SeekOrigin.Begin); + + BsonReader reader = new BsonReader(ms); + reader.ReadRootValueAsArray = true; + reader.DateTimeKindHandling = DateTimeKind.Utc; + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Date, reader.TokenType); + Assert.AreEqual(new DateTime(2000, 10, 12, 20, 55, 0, DateTimeKind.Utc), reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Date, reader.TokenType); + Assert.AreEqual(new DateTime(2000, 10, 12, 20, 55, 0, DateTimeKind.Utc), reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Date, reader.TokenType); + Assert.AreEqual(new DateTime(2000, 10, 12, 20, 55, 0, DateTimeKind.Utc), reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndArray, reader.TokenType); + + Assert.IsFalse(reader.Read()); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 4555ec7f..d31ab1d1 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -34,8 +34,8 @@ namespace Newtonsoft.Json.Tests public class PerformanceTests : TestFixtureBase { - //private const int Iterations = 100; - private const int Iterations = 5000; + private const int Iterations = 100; + //private const int Iterations = 5000; #region Data private const string BsonHex = @@ -508,7 +508,7 @@ namespace Newtonsoft.Json.Tests TimeOperation(() => { - for (int i = 0; i < 5000; i++) + for (int i = 0; i < Iterations; i++) { test["dummy"] = new JValue(i); Encoding.UTF8.GetBytes(test.ToString(Formatting.None)); @@ -526,7 +526,7 @@ namespace Newtonsoft.Json.Tests TimeOperation(() => { - for (int i = 0; i < 5000; i++) + for (int i = 0; i < Iterations; i++) { test["dummy"] = new JValue(i); ms.Seek(0, SeekOrigin.Begin); diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 91d60670..5c5a84f2 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -1012,7 +1012,7 @@ keyword such as type of business."" } [Test] - public void RequiredMembersClassWithNullValues() + public void DeserializeRequiredMembersClassWithNullValues() { string json = @"{ ""FirstName"": ""I can't be null bro!"", @@ -1030,7 +1030,7 @@ keyword such as type of business."" [Test] [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "Required property 'FirstName' expects a value but got null.")] - public void RequiredMembersClassNullRequiredValueProperty() + public void DeserializeRequiredMembersClassNullRequiredValueProperty() { string json = @"{ ""FirstName"": null, @@ -1042,6 +1042,22 @@ keyword such as type of business."" JsonConvert.DeserializeObject(json); } + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "Cannot write a null value for property 'FirstName'. Property requires a value.")] + public void SerializeRequiredMembersClassNullRequiredValueProperty() + { + RequiredMembersClass requiredMembersClass = new RequiredMembersClass + { + FirstName = null, + BirthDate = new DateTime(2000, 10, 10, 10, 10, 10, DateTimeKind.Utc), + LastName = null, + MiddleName = null + }; + + string json = JsonConvert.SerializeObject(requiredMembersClass); + Console.WriteLine(json); + } + [Test] [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "Required property 'LastName' not found in JSON.")] public void RequiredMembersClassMissingRequiredProperty() diff --git a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs index 382e0d2e..06b0315e 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs @@ -461,60 +461,82 @@ namespace Newtonsoft.Json.Tests.Serialization } #endif - //[Test] - //[Ignore("TODO")] - //public void CollectionWithAbstractItems() - //{ - // string Text = ""; + [Test] + public void CollectionWithAbstractItems() + { + HolderClass testObject = new HolderClass(); + testObject.TestMember = new ContentSubClass("First One"); + testObject.AnotherTestMember = new Dictionary>(); + testObject.AnotherTestMember.Add(1, new List()); + testObject.AnotherTestMember[1].Add(new ContentSubClass("Second One")); + testObject.AThirdTestMember = new ContentSubClass("Third One"); - // HolderClass testObject = new HolderClass(); - // testObject.TestMember = new ContentSubClass("First One"); - // testObject.AnotherTestMember = new Dictionary>(); - // testObject.AnotherTestMember.Add(1, new List()); - // testObject.AnotherTestMember[1].Add(new ContentSubClass("Second One")); - // testObject.AThirdTestMember = new ContentSubClass("Third One"); - // HolderClass anotherTestObject = null; + JsonSerializer serializingTester = new JsonSerializer(); + serializingTester.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - // Newtonsoft.Json.JsonSerializer serializingTester = new Newtonsoft.Json.JsonSerializer(); - // serializingTester.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; + StringWriter sw = new StringWriter(); + using (JsonTextWriter jsonWriter = new JsonTextWriter(sw)) + { + jsonWriter.Formatting = Formatting.Indented; + serializingTester.TypeNameHandling = TypeNameHandling.Auto; + serializingTester.Serialize(jsonWriter, testObject); + } - // StringWriter sw = new StringWriter(); - // using (Newtonsoft.Json.JsonTextWriter jsonWriter = new Newtonsoft.Json.JsonTextWriter(sw)) - // { - // jsonWriter.Formatting = Newtonsoft.Json.Formatting.Indented; + string json = sw.ToString(); - // if (Text.Equals("Properties All, Serializer All")) - // serializingTester.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All; - // else if (Text.Equals("Properties All, Serializer Objects")) - // serializingTester.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects; - // else if (Text.Equals("Properties All, Serializer Arrays")) - // serializingTester.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Arrays; - // //otherwise leave default, which looks like it's "None" + string contentSubClassRef = ReflectionUtils.GetTypeName(typeof(ContentSubClass), FormatterAssemblyStyle.Simple); + string dictionaryRef = ReflectionUtils.GetTypeName(typeof(Dictionary>), FormatterAssemblyStyle.Simple); + string listRef = ReflectionUtils.GetTypeName(typeof(List), FormatterAssemblyStyle.Simple); - // serializingTester.Serialize(jsonWriter, testObject); - // } - // string json = sw.ToString(); + Assert.AreEqual(@"{ + ""TestMember"": { + ""$type"": """ + contentSubClassRef + @""", + ""SomeString"": ""First One"" + }, + ""AnotherTestMember"": { + ""$type"": """ + dictionaryRef + @""", + ""1"": { + ""$type"": """ + listRef + @""", + ""$values"": [ + { + ""$type"": """ + contentSubClassRef + @""", + ""SomeString"": ""Second One"" + } + ] + } + }, + ""AThirdTestMember"": { + ""$type"": """ + contentSubClassRef + @""", + ""SomeString"": ""Third One"" + } +}", json); + Console.WriteLine(json); - // Console.WriteLine(json); + StringReader sr = new StringReader(json); - // StringReader sr = new StringReader(json); + JsonSerializer deserializingTester = new JsonSerializer(); - // Newtonsoft.Json.JsonSerializer deserializingTester = new Newtonsoft.Json.JsonSerializer(); + HolderClass anotherTestObject; - // using (Newtonsoft.Json.JsonTextReader jsonReader = new Newtonsoft.Json.JsonTextReader(sr)) - // { - // if (Text.Equals("Properties All, Serializer All")) - // deserializingTester.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All; - // else if (Text.Equals("Properties All, Serializer Objects")) - // deserializingTester.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects; - // else if (Text.Equals("Properties All, Serializer Arrays")) - // deserializingTester.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Arrays; - // //otherwise leave default, which looks like it's "None" + using (JsonTextReader jsonReader = new JsonTextReader(sr)) + { + deserializingTester.TypeNameHandling = TypeNameHandling.Auto; - // anotherTestObject = deserializingTester.Deserialize(jsonReader); - // } - //} + anotherTestObject = deserializingTester.Deserialize(jsonReader); + } + + Assert.IsNotNull(anotherTestObject); + Assert.IsInstanceOfType(typeof(ContentSubClass), anotherTestObject.TestMember); + Assert.IsInstanceOfType(typeof(Dictionary>), anotherTestObject.AnotherTestMember); + Assert.AreEqual(1, anotherTestObject.AnotherTestMember.Count); + + IList list = anotherTestObject.AnotherTestMember[1]; + + Assert.IsInstanceOfType(typeof(List), list); + Assert.AreEqual(1, list.Count); + Assert.IsInstanceOfType(typeof(ContentSubClass), list[0]); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/HolderClass.cs b/Src/Newtonsoft.Json.Tests/TestObjects/HolderClass.cs index 27b26f4b..14bcd5e8 100644 --- a/Src/Newtonsoft.Json.Tests/TestObjects/HolderClass.cs +++ b/Src/Newtonsoft.Json.Tests/TestObjects/HolderClass.cs @@ -13,7 +13,7 @@ namespace Newtonsoft.Json.Tests.TestObjects public ContentBaseClass TestMember { get; set; } [Newtonsoft.Json.JsonProperty(TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All)] - public Dictionary> AnotherTestMember { get; set; } + public Dictionary> AnotherTestMember { get; set; } public ContentBaseClass AThirdTestMember { get; set; } diff --git a/Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs b/Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs index a391e61e..84a7ffbd 100644 --- a/Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs +++ b/Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs @@ -17,8 +17,11 @@ namespace Newtonsoft.Json.Bson private byte[] _largeByteBuffer; private int _maxChars; + public DateTimeKind DateTimeKindHandling { get; set; } + public BsonBinaryWriter(Stream stream) { + DateTimeKindHandling = DateTimeKind.Utc; _writer = new BinaryWriter(stream); } @@ -107,8 +110,12 @@ namespace Newtonsoft.Json.Bson if (value.Value is DateTime) { DateTime dateTime = (DateTime) value.Value; - dateTime = dateTime.ToUniversalTime(); - ticks = JsonConvert.ConvertDateTimeToJavaScriptTicks(dateTime); + if (DateTimeKindHandling == DateTimeKind.Utc) + dateTime = dateTime.ToUniversalTime(); + else if (DateTimeKindHandling == DateTimeKind.Local) + dateTime = dateTime.ToLocalTime(); + + ticks = JsonConvert.ConvertDateTimeToJavaScriptTicks(dateTime, false); } #if !PocketPC && !NET20 else diff --git a/Src/Newtonsoft.Json/Bson/BsonReader.cs b/Src/Newtonsoft.Json/Bson/BsonReader.cs index 9f25b71d..4b943571 100644 --- a/Src/Newtonsoft.Json/Bson/BsonReader.cs +++ b/Src/Newtonsoft.Json/Bson/BsonReader.cs @@ -572,23 +572,27 @@ namespace Newtonsoft.Json.Bson StringBuilder builder = null; int totalBytesRead = 0; + + // used in case of left over multibyte characters in the buffer int offset = 0; do { - // read up to the maximum size of the buffer or what is remaining, minus the offset - // of left over multibyte chars int count = ((length - totalBytesRead) > MaxCharBytesSize - offset) ? MaxCharBytesSize - offset - : length - totalBytesRead - offset; + : length - totalBytesRead; int byteCount = _reader.BaseStream.Read(_byteBuffer, offset, count); if (byteCount == 0) throw new EndOfStreamException("Unable to read beyond the end of the stream."); + totalBytesRead += byteCount; + + // Above, byteCount is how many bytes we read this time. + // Below, byteCount is how many bytes are in the _byteBuffer. byteCount += offset; - if (totalBytesRead == 0 && byteCount == length) + if (byteCount == length) { // pref optimization to avoid reading into a string builder // first iteration and all bytes read then return string directly @@ -615,8 +619,6 @@ namespace Newtonsoft.Json.Bson { offset = 0; } - - totalBytesRead += (byteCount - offset); } } while (totalBytesRead < length); diff --git a/Src/Newtonsoft.Json/Bson/BsonWriter.cs b/Src/Newtonsoft.Json/Bson/BsonWriter.cs index 53821221..d3a380e8 100644 --- a/Src/Newtonsoft.Json/Bson/BsonWriter.cs +++ b/Src/Newtonsoft.Json/Bson/BsonWriter.cs @@ -45,6 +45,17 @@ namespace Newtonsoft.Json.Bson private BsonToken _parent; private string _propertyName; + /// + /// Gets or sets the used when writing values to BSON. + /// When set to no conversion will occur. + /// + /// The used when writing values to BSON. + public DateTimeKind DateTimeKindHandling + { + get { return _writer.DateTimeKindHandling; } + set { _writer.DateTimeKindHandling = value; } + } + /// /// Initializes a new instance of the class. /// diff --git a/Src/Newtonsoft.Json/JsonConvert.cs b/Src/Newtonsoft.Json/JsonConvert.cs index 40bdff45..c824cadc 100644 --- a/Src/Newtonsoft.Json/JsonConvert.cs +++ b/Src/Newtonsoft.Json/JsonConvert.cs @@ -181,9 +181,14 @@ namespace Newtonsoft.Json internal static long ConvertDateTimeToJavaScriptTicks(DateTime dateTime) { - long universialTicks = ToUniversalTicks(dateTime); + return ConvertDateTimeToJavaScriptTicks(dateTime, true); + } - return UniversialTicksToJavaScriptTicks(universialTicks); + internal static long ConvertDateTimeToJavaScriptTicks(DateTime dateTime, bool convertToUtc) + { + long ticks = (convertToUtc) ? ToUniversalTicks(dateTime) : dateTime.Ticks; + + return UniversialTicksToJavaScriptTicks(ticks); } private static long UniversialTicksToJavaScriptTicks(long universialTicks) diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index 655db63f..e711c9f4 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -109,7 +109,7 @@ namespace Newtonsoft.Json get { return _typeNameHandling; } set { - if (value < TypeNameHandling.None || value > TypeNameHandling.All) + if (value < TypeNameHandling.None || value > TypeNameHandling.Auto) throw new ArgumentOutOfRangeException("value"); _typeNameHandling = value; diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index ddb67db3..33e364ff 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -62,7 +62,7 @@ namespace Newtonsoft.Json.Serialization if (jsonWriter == null) throw new ArgumentNullException("jsonWriter"); - SerializeValue(jsonWriter, value, null, GetContractSafe(value)); + SerializeValue(jsonWriter, value, GetContractSafe(value), null, null); } private JsonSerializerProxy GetInternalSerializer() @@ -81,7 +81,7 @@ namespace Newtonsoft.Json.Serialization return Serializer.ContractResolver.ResolveContract(value.GetType()); } - private void SerializeValue(JsonWriter writer, object value, JsonProperty member, JsonContract contract) + private void SerializeValue(JsonWriter writer, object value, JsonContract valueContract, JsonProperty member, JsonContract collectionValueContract) { JsonConverter converter = (member != null) ? member.Converter : null; @@ -92,39 +92,39 @@ namespace Newtonsoft.Json.Serialization } if ((converter != null - || ((converter = contract.Converter) != null) - || ((converter = Serializer.GetMatchingConverter(contract.UnderlyingType)) != null) - || ((converter = contract.InternalConverter) != null)) + || ((converter = valueContract.Converter) != null) + || ((converter = Serializer.GetMatchingConverter(valueContract.UnderlyingType)) != null) + || ((converter = valueContract.InternalConverter) != null)) && converter.CanWrite) { - SerializeConvertable(writer, converter, value, contract); + SerializeConvertable(writer, converter, value, valueContract); } - else if (contract is JsonPrimitiveContract) + else if (valueContract is JsonPrimitiveContract) { writer.WriteValue(value); } - else if (contract is JsonStringContract) + else if (valueContract is JsonStringContract) { - SerializeString(writer, value, (JsonStringContract) contract); + SerializeString(writer, value, (JsonStringContract) valueContract); } - else if (contract is JsonObjectContract) + else if (valueContract is JsonObjectContract) { - SerializeObject(writer, value, (JsonObjectContract) contract, member); + SerializeObject(writer, value, (JsonObjectContract)valueContract, member, collectionValueContract); } - else if (contract is JsonDictionaryContract) + else if (valueContract is JsonDictionaryContract) { - JsonDictionaryContract dictionaryContract = (JsonDictionaryContract) contract; - SerializeDictionary(writer, dictionaryContract.CreateWrapper(value), dictionaryContract, member); + JsonDictionaryContract dictionaryContract = (JsonDictionaryContract) valueContract; + SerializeDictionary(writer, dictionaryContract.CreateWrapper(value), dictionaryContract, member, collectionValueContract); } - else if (contract is JsonArrayContract) + else if (valueContract is JsonArrayContract) { if (value is IList) { - SerializeList(writer, (IList) value, (JsonArrayContract) contract, member); + SerializeList(writer, (IList)value, (JsonArrayContract)valueContract, member, collectionValueContract); } else if (value is IEnumerable) { - SerializeList(writer, ((IEnumerable)value).Cast().ToList(), (JsonArrayContract)contract, member); + SerializeList(writer, ((IEnumerable)value).Cast().ToList(), (JsonArrayContract)valueContract, member, collectionValueContract); } else { @@ -133,14 +133,14 @@ namespace Newtonsoft.Json.Serialization CultureInfo.InvariantCulture, value.GetType())); } } - else if (contract is JsonLinqContract) + else if (valueContract is JsonLinqContract) { ((JToken)value).WriteTo(writer, (Serializer.Converters != null) ? Serializer.Converters.ToArray() : null); } #if !SILVERLIGHT && !PocketPC - else if (contract is JsonISerializableContract) + else if (valueContract is JsonISerializableContract) { - SerializeISerializable(writer, (ISerializable) value, (JsonISerializableContract) contract); + SerializeISerializable(writer, (ISerializable) value, (JsonISerializableContract) valueContract); } #endif } @@ -198,8 +198,11 @@ namespace Newtonsoft.Json.Serialization if (!CheckForCircularReference(memberValue, property.ReferenceLoopHandling, contract)) return; + if (memberValue == null && property.Required == Required.Always) + throw new JsonSerializationException("Cannot write a null value for property '{0}'. Property requires a value.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); + writer.WritePropertyName(propertyName); - SerializeValue(writer, memberValue, property, contract); + SerializeValue(writer, memberValue, contract, property, null); } private bool CheckForCircularReference(object value, ReferenceLoopHandling? referenceLoopHandling, JsonContract contract) @@ -286,7 +289,7 @@ namespace Newtonsoft.Json.Serialization contract.InvokeOnSerialized(value, Serializer.Context); } - private void SerializeObject(JsonWriter writer, object value, JsonObjectContract contract, JsonProperty member) + private void SerializeObject(JsonWriter writer, object value, JsonObjectContract contract, JsonProperty member, JsonContract collectionValueContract) { contract.InvokeOnSerializing(value, Serializer.Context); @@ -299,7 +302,7 @@ namespace Newtonsoft.Json.Serialization writer.WritePropertyName(JsonTypeReflector.IdPropertyName); writer.WriteValue(Serializer.ReferenceResolver.GetReference(value)); } - if (HasFlag(((member != null) ? member.TypeNameHandling : null) ?? Serializer.TypeNameHandling, TypeNameHandling.Objects)) + if (ShouldWriteType(TypeNameHandling.Objects, contract, member, collectionValueContract)) { WriteTypeProperty(writer, contract.UnderlyingType); } @@ -368,14 +371,14 @@ namespace Newtonsoft.Json.Serialization } } - private void SerializeList(JsonWriter writer, IList values, JsonArrayContract contract, JsonProperty member) + private void SerializeList(JsonWriter writer, IList values, JsonArrayContract contract, JsonProperty member, JsonContract collectionValueContract) { contract.InvokeOnSerializing(values, Serializer.Context); SerializeStack.Add(values); bool isReference = contract.IsReference ?? HasFlag(Serializer.PreserveReferencesHandling, PreserveReferencesHandling.Arrays); - bool includeTypeDetails = HasFlag(((member != null) ? member.TypeNameHandling : null) ?? Serializer.TypeNameHandling, TypeNameHandling.Arrays); + bool includeTypeDetails = ShouldWriteType(TypeNameHandling.Arrays, contract, member, collectionValueContract); if (isReference || includeTypeDetails) { @@ -393,6 +396,8 @@ namespace Newtonsoft.Json.Serialization writer.WritePropertyName(JsonTypeReflector.ArrayValuesPropertyName); } + JsonContract childValuesContract = Serializer.ContractResolver.ResolveContract(contract.CollectionItemType ?? typeof(object)); + writer.WriteStartArray(); int initialDepth = writer.Top; @@ -413,7 +418,7 @@ namespace Newtonsoft.Json.Serialization if (!CheckForCircularReference(value, null, contract)) continue; - SerializeValue(writer, value, null, valueContract); + SerializeValue(writer, value, valueContract, null, childValuesContract); } } catch (Exception ex) @@ -451,7 +456,7 @@ namespace Newtonsoft.Json.Serialization foreach (SerializationEntry serializationEntry in serializationInfo) { writer.WritePropertyName(serializationEntry.Name); - SerializeValue(writer, serializationEntry.Value, null, GetContractSafe(serializationEntry.Value)); + SerializeValue(writer, serializationEntry.Value, GetContractSafe(serializationEntry.Value), null, null); } writer.WriteEndObject(); @@ -461,7 +466,39 @@ namespace Newtonsoft.Json.Serialization } #endif - private void SerializeDictionary(JsonWriter writer, IWrappedDictionary values, JsonDictionaryContract contract, JsonProperty member) + //private bool ShouldWriteTypeProperty(JsonProperty member, JsonContract contract, TypeNameHandling typeFlag) + //{ + // if (HasFlag(((member != null) ? member.TypeNameHandling : null) ?? Serializer.TypeNameHandling, typeFlag)) + // return true; + + // if ((((member != null) ? member.TypeNameHandling : null) ?? Serializer.TypeNameHandling) == TypeNameHandling.Auto) + + // || (member != null + // && (member.TypeNameHandling ?? Serializer.TypeNameHandling) == TypeNameHandling.Auto + // && contract.UnderlyingType != member.PropertyType) + // ) + //} + + private bool ShouldWriteType(TypeNameHandling typeNameHandlingFlag, JsonContract contract, JsonProperty member, JsonContract collectionValueContract) + { + if (HasFlag(((member != null) ? member.TypeNameHandling : null) ?? Serializer.TypeNameHandling, typeNameHandlingFlag)) + return true; + + if (member != null) + { + if ((member.TypeNameHandling ?? Serializer.TypeNameHandling) == TypeNameHandling.Auto && contract.UnderlyingType != member.PropertyType) + return true; + } + else if (collectionValueContract != null) + { + if (Serializer.TypeNameHandling == TypeNameHandling.Auto && contract.UnderlyingType != collectionValueContract.UnderlyingType) + return true; + } + + return false; + } + + private void SerializeDictionary(JsonWriter writer, IWrappedDictionary values, JsonDictionaryContract contract, JsonProperty member, JsonContract collectionValueContract) { contract.InvokeOnSerializing(values.UnderlyingDictionary, Serializer.Context); @@ -474,11 +511,13 @@ namespace Newtonsoft.Json.Serialization writer.WritePropertyName(JsonTypeReflector.IdPropertyName); writer.WriteValue(Serializer.ReferenceResolver.GetReference(values.UnderlyingDictionary)); } - if (HasFlag(((member != null) ? member.TypeNameHandling : null) ?? Serializer.TypeNameHandling, TypeNameHandling.Objects)) + if (ShouldWriteType(TypeNameHandling.Objects, contract, member, collectionValueContract)) { WriteTypeProperty(writer, values.UnderlyingDictionary.GetType()); } + JsonContract childValuesContract = Serializer.ContractResolver.ResolveContract(contract.DictionaryValueType ?? typeof(object)); + int initialDepth = writer.Top; // Mono Unity 3.0 fix @@ -505,7 +544,7 @@ namespace Newtonsoft.Json.Serialization writer.WritePropertyName(propertyName); - SerializeValue(writer, value, null, valueContract); + SerializeValue(writer, value, valueContract, null, childValuesContract); } } catch (Exception ex) diff --git a/Src/Newtonsoft.Json/TypeNameHandling.cs b/Src/Newtonsoft.Json/TypeNameHandling.cs index d6ddd52d..2a80c9ca 100644 --- a/Src/Newtonsoft.Json/TypeNameHandling.cs +++ b/Src/Newtonsoft.Json/TypeNameHandling.cs @@ -24,6 +24,10 @@ namespace Newtonsoft.Json /// Arrays = 2, /// + /// Include the .NET type name when the type of the object being serialized is not the same as its declared type. + /// + Auto = 4, + /// /// Always include the .NET type name when serializing. /// All = Objects | Arrays