From 32dc957880dedf14014628bc66c5e3ec1fbf729b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 31 Jul 2012 20:53:26 +1200 Subject: [PATCH] -Properties on dynamic objects with JsonProperty/DataMember will now be included in serialized JSON -Serialize and deserialize multidimensional arrays --- .../Properties/AssemblyInfo.cs | 2 +- ...lCasePropertyNamesContractResolverTests.cs | 1 + .../Serialization/DynamicTests.cs | 7 + .../Serialization/JsonSerializerTest.cs | 194 ++++++++++++++++ .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/DefaultContractResolver.cs | 16 +- .../Serialization/JsonArrayContract.cs | 4 + .../Serialization/JsonDynamicContract.cs | 2 +- .../Serialization/JsonProperty.cs | 6 + .../JsonSerializerInternalReader.cs | 139 +++++++++++- .../JsonSerializerInternalWriter.cs | 210 ++++++++++++++---- .../Utilities/CollectionUtils.cs | 78 +++++++ 12 files changed, 602 insertions(+), 59 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index fbe53564..e97dea47 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -72,5 +72,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.5.7.15127")] +[assembly: AssemblyFileVersion("4.5.8.15131")] #endif diff --git a/Src/Newtonsoft.Json.Tests/Serialization/CamelCasePropertyNamesContractResolverTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/CamelCasePropertyNamesContractResolverTests.cs index cf83dbdb..e99f6511 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/CamelCasePropertyNamesContractResolverTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/CamelCasePropertyNamesContractResolverTests.cs @@ -192,6 +192,7 @@ namespace Newtonsoft.Json.Tests.Serialization }); Assert.AreEqual(@"{ + ""explicit"": false, ""text"": ""Text!"", ""integer"": 2147483647, ""int"": 0, diff --git a/Src/Newtonsoft.Json.Tests/Serialization/DynamicTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/DynamicTests.cs index efd0bbc8..bd0d466b 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/DynamicTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/DynamicTests.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization.Formatters; using System.Text; @@ -60,6 +61,8 @@ namespace Newtonsoft.Json.Tests.Serialization private readonly Dictionary _members; public int Int; + [JsonProperty] + public bool Explicit; public DynamicChildObject ChildObject { get; set; } internal Dictionary Members @@ -122,6 +125,7 @@ namespace Newtonsoft.Json.Tests.Serialization public void SerializeDynamicObject() { TestDynamicObject dynamicObject = new TestDynamicObject(); + dynamicObject.Explicit = true; dynamic d = dynamicObject; d.Int = 1; @@ -144,6 +148,7 @@ namespace Newtonsoft.Json.Tests.Serialization string json = JsonConvert.SerializeObject(dynamicObject, Formatting.Indented); Assert.AreEqual(@"{ + ""Explicit"": true, ""Decimal"": 99.9, ""Int"": 1, ""ChildObject"": { @@ -153,6 +158,8 @@ namespace Newtonsoft.Json.Tests.Serialization }", json); TestDynamicObject newDynamicObject = JsonConvert.DeserializeObject(json); + Assert.AreEqual(true, newDynamicObject.Explicit); + d = newDynamicObject; Assert.AreEqual(99.9, d.Decimal); diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 3f16b678..0d1ccb31 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -6629,6 +6629,186 @@ Parameter name: value", } } #endif + + [Test] + public void SerializeArray2D() + { + Array2D aa = new Array2D(); + aa.Before = "Before!"; + aa.After = "After!"; + aa.Coordinates = new[,] { { 1, 1 }, { 1, 2 }, { 2, 1 }, { 2, 2 } }; + + string json = JsonConvert.SerializeObject(aa); + + Assert.AreEqual(@"{""Before"":""Before!"",""Coordinates"":[[1,1],[1,2],[2,1],[2,2]],""After"":""After!""}", json); + } + + [Test] + public void SerializeArray3D() + { + Array3D aa = new Array3D(); + aa.Before = "Before!"; + aa.After = "After!"; + aa.Coordinates = new[, ,] { { { 1, 1, 1 }, { 1, 1, 2 } }, { { 1, 2, 1 }, { 1, 2, 2 } }, { { 2, 1, 1 }, { 2, 1, 2 } }, { { 2, 2, 1 }, { 2, 2, 2 } } }; + + string json = JsonConvert.SerializeObject(aa); + + Assert.AreEqual(@"{""Before"":""Before!"",""Coordinates"":[[[1,1,1],[1,1,2]],[[1,2,1],[1,2,2]],[[2,1,1],[2,1,2]],[[2,2,1],[2,2,2]]],""After"":""After!""}", json); + } + + [Test] + public void DeserializeArray2D() + { + string json = @"{""Before"":""Before!"",""Coordinates"":[[1,1],[1,2],[2,1],[2,2]],""After"":""After!""}"; + + Array2D aa = JsonConvert.DeserializeObject(json); + + Assert.AreEqual("Before!", aa.Before); + Assert.AreEqual("After!", aa.After); + Assert.AreEqual(4, aa.Coordinates.GetLength(0)); + Assert.AreEqual(2, aa.Coordinates.GetLength(1)); + Assert.AreEqual(1, aa.Coordinates[0, 0]); + Assert.AreEqual(2, aa.Coordinates[1, 1]); + + string after = JsonConvert.SerializeObject(aa); + + Assert.AreEqual(json, after); + } + + [Test] + public void DeserializeArray2D_WithTooManyItems() + { + string json = @"{""Before"":""Before!"",""Coordinates"":[[1,1],[1,2,3],[2,1],[2,2]],""After"":""After!""}"; + + ExceptionAssert.Throws( + "Cannot deserialize non-cubical array as multidimensional array.", + () => JsonConvert.DeserializeObject(json)); + } + + [Test] + public void DeserializeArray2D_WithTooFewItems() + { + string json = @"{""Before"":""Before!"",""Coordinates"":[[1,1],[1],[2,1],[2,2]],""After"":""After!""}"; + + ExceptionAssert.Throws( + "Cannot deserialize non-cubical array as multidimensional array.", + () => JsonConvert.DeserializeObject(json)); + } + + [Test] + public void DeserializeArray3D() + { + string json = @"{""Before"":""Before!"",""Coordinates"":[[[1,1,1],[1,1,2]],[[1,2,1],[1,2,2]],[[2,1,1],[2,1,2]],[[2,2,1],[2,2,2]]],""After"":""After!""}"; + + Array3D aa = JsonConvert.DeserializeObject(json); + + Assert.AreEqual("Before!", aa.Before); + Assert.AreEqual("After!", aa.After); + Assert.AreEqual(4, aa.Coordinates.GetLength(0)); + Assert.AreEqual(2, aa.Coordinates.GetLength(1)); + Assert.AreEqual(3, aa.Coordinates.GetLength(2)); + Assert.AreEqual(1, aa.Coordinates[0, 0, 0]); + Assert.AreEqual(2, aa.Coordinates[1, 1, 1]); + + string after = JsonConvert.SerializeObject(aa); + + Assert.AreEqual(json, after); + } + + [Test] + public void DeserializeArray3D_WithTooManyItems() + { + string json = @"{""Before"":""Before!"",""Coordinates"":[[[1,1,1],[1,1,2,1]],[[1,2,1],[1,2,2]],[[2,1,1],[2,1,2]],[[2,2,1],[2,2,2]]],""After"":""After!""}"; + + ExceptionAssert.Throws( + "Cannot deserialize non-cubical array as multidimensional array.", + () => JsonConvert.DeserializeObject(json)); + } + + [Test] + public void DeserializeArray3D_WithBadItems() + { + string json = @"{""Before"":""Before!"",""Coordinates"":[[[1,1,1],[1,1,2]],[[1,2,1],[1,2,2]],[[2,1,1],[2,1,2]],[[2,2,1],{}]],""After"":""After!""}"; + + ExceptionAssert.Throws( + "Unexpected token when deserializing multidimensional array: StartObject. Path 'Coordinates[3][1]', line 1, position 99.", + () => JsonConvert.DeserializeObject(json)); + } + + [Test] + public void DeserializeArray3D_WithTooFewItems() + { + string json = @"{""Before"":""Before!"",""Coordinates"":[[[1,1,1],[1,1]],[[1,2,1],[1,2,2]],[[2,1,1],[2,1,2]],[[2,2,1],[2,2,2]]],""After"":""After!""}"; + + ExceptionAssert.Throws( + "Cannot deserialize non-cubical array as multidimensional array.", + () => JsonConvert.DeserializeObject(json)); + } + + [Test] + public void SerializeEmpty3DArray() + { + Array3D aa = new Array3D(); + aa.Before = "Before!"; + aa.After = "After!"; + aa.Coordinates = new int[0, 0, 0]; + + string json = JsonConvert.SerializeObject(aa); + + Assert.AreEqual(@"{""Before"":""Before!"",""Coordinates"":[],""After"":""After!""}", json); + } + + [Test] + public void DeserializeEmpty3DArray() + { + string json = @"{""Before"":""Before!"",""Coordinates"":[],""After"":""After!""}"; + + Array3D aa = JsonConvert.DeserializeObject(json); + + Assert.AreEqual("Before!", aa.Before); + Assert.AreEqual("After!", aa.After); + Assert.AreEqual(0, aa.Coordinates.GetLength(0)); + Assert.AreEqual(0, aa.Coordinates.GetLength(1)); + Assert.AreEqual(0, aa.Coordinates.GetLength(2)); + + string after = JsonConvert.SerializeObject(aa); + + Assert.AreEqual(json, after); + } + + [Test] + public void DeserializeNull3DArray() + { + string json = @"{""Before"":""Before!"",""Coordinates"":null,""After"":""After!""}"; + + Array3D aa = JsonConvert.DeserializeObject(json); + + Assert.AreEqual("Before!", aa.Before); + Assert.AreEqual("After!", aa.After); + Assert.AreEqual(null, aa.Coordinates); + + string after = JsonConvert.SerializeObject(aa); + + Assert.AreEqual(json, after); + } + + [Test] + public void DeserializeSemiEmpty3DArray() + { + string json = @"{""Before"":""Before!"",""Coordinates"":[[]],""After"":""After!""}"; + + Array3D aa = JsonConvert.DeserializeObject(json); + + Assert.AreEqual("Before!", aa.Before); + Assert.AreEqual("After!", aa.After); + Assert.AreEqual(1, aa.Coordinates.GetLength(0)); + Assert.AreEqual(0, aa.Coordinates.GetLength(1)); + Assert.AreEqual(0, aa.Coordinates.GetLength(2)); + + string after = JsonConvert.SerializeObject(aa); + + Assert.AreEqual(json, after); + } } #if !(SILVERLIGHT || NETFX_CORE || PORTABLE) @@ -6939,4 +7119,18 @@ Parameter name: value", public string IgnoreDataMemberAndDataMemberAttribute { get; set; } } #endif + + public class Array2D + { + public string Before { get; set; } + public int[,] Coordinates { get; set; } + public string After { get; set; } + } + + public class Array3D + { + public string Before { get; set; } + public int[,,] Coordinates { get; set; } + public string After { get; set; } + } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index fb7d9e65..7f6a12f6 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -91,7 +91,7 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.5.7.15127")] +[assembly: AssemblyFileVersion("4.5.8.15131")] #endif [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 407f819e..2e0ab290 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -457,8 +457,7 @@ namespace Newtonsoft.Json.Serialization property.PropertyType = parameterInfo.ParameterType; bool allowNonPublicAccess; - bool hasExplicitAttribute; - SetPropertySettingsFromAttributes(property, parameterInfo.GetCustomAttributeProvider(), parameterInfo.Name, parameterInfo.Member.DeclaringType, MemberSerialization.OptOut, out allowNonPublicAccess, out hasExplicitAttribute); + SetPropertySettingsFromAttributes(property, parameterInfo.GetCustomAttributeProvider(), parameterInfo.Name, parameterInfo.Member.DeclaringType, MemberSerialization.OptOut, out allowNonPublicAccess); property.Readable = false; property.Writable = true; @@ -905,13 +904,12 @@ namespace Newtonsoft.Json.Serialization property.ValueProvider = CreateMemberValueProvider(member); bool allowNonPublicAccess; - bool hasExplicitAttribute; - SetPropertySettingsFromAttributes(property, member.GetCustomAttributeProvider(), member.Name, member.DeclaringType, memberSerialization, out allowNonPublicAccess, out hasExplicitAttribute); + SetPropertySettingsFromAttributes(property, member.GetCustomAttributeProvider(), member.Name, member.DeclaringType, memberSerialization, out allowNonPublicAccess); if (memberSerialization != MemberSerialization.Fields) { property.Readable = ReflectionUtils.CanReadMemberValue(member, allowNonPublicAccess); - property.Writable = ReflectionUtils.CanSetMemberValue(member, allowNonPublicAccess, hasExplicitAttribute); + property.Writable = ReflectionUtils.CanSetMemberValue(member, allowNonPublicAccess, property.HasMemberAttribute); } else { @@ -926,10 +924,8 @@ namespace Newtonsoft.Json.Serialization return property; } - private void SetPropertySettingsFromAttributes(JsonProperty property, ICustomAttributeProvider attributeProvider, string name, Type declaringType, MemberSerialization memberSerialization, out bool allowNonPublicAccess, out bool hasExplicitAttribute) + private void SetPropertySettingsFromAttributes(JsonProperty property, ICustomAttributeProvider attributeProvider, string name, Type declaringType, MemberSerialization memberSerialization, out bool allowNonPublicAccess) { - hasExplicitAttribute = false; - #if !PocketPC && !NET20 DataContractAttribute dataContractAttribute = JsonTypeReflector.GetDataContractAttribute(declaringType); @@ -949,7 +945,7 @@ namespace Newtonsoft.Json.Serialization JsonPropertyAttribute propertyAttribute = JsonTypeReflector.GetAttribute(attributeProvider); if (propertyAttribute != null) - hasExplicitAttribute = true; + property.HasMemberAttribute = true; string mappedName; if (propertyAttribute != null && propertyAttribute.PropertyName != null) @@ -1039,7 +1035,7 @@ namespace Newtonsoft.Json.Serialization if (dataMemberAttribute != null) { allowNonPublicAccess = true; - hasExplicitAttribute = true; + property.HasMemberAttribute = true; } #endif } diff --git a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs index 32299a5b..e04bf6c7 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs @@ -47,6 +47,8 @@ namespace Newtonsoft.Json.Serialization /// The of the collection items. public Type CollectionItemType { get; private set; } + public bool IsMultidimensionalArray { get; private set; } + private readonly bool _isCollectionItemTypeNullableType; private readonly Type _genericCollectionDefinitionType; private Type _genericWrapperType; @@ -82,6 +84,8 @@ namespace Newtonsoft.Json.Serialization { CreatedType = ReflectionUtils.MakeGenericType(typeof(List<>), CollectionItemType); } + + IsMultidimensionalArray = (UnderlyingType.IsArray && UnderlyingType.GetArrayRank() > 1); } internal IWrappedCollection CreateWrapper(object list) diff --git a/Src/Newtonsoft.Json/Serialization/JsonDynamicContract.cs b/Src/Newtonsoft.Json/Serialization/JsonDynamicContract.cs index 1a3ff5c5..6b5b8982 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonDynamicContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonDynamicContract.cs @@ -35,7 +35,7 @@ namespace Newtonsoft.Json.Serialization /// /// Contract details for a used by the . /// - public class JsonDynamicContract : JsonContract + public class JsonDynamicContract : JsonContainerContract { /// /// Gets the object's properties. diff --git a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs index eb33e6d9..5c39cbee 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs @@ -107,6 +107,12 @@ namespace Newtonsoft.Json.Serialization /// true if writable; otherwise, false. public bool Writable { get; set; } + /// + /// Gets a value indicating whether this has a member attribute. + /// + /// true if has a member attribute; otherwise, false. + public bool HasMemberAttribute { get; set; } + /// /// Gets the default value. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index c55f8b78..b2be9b0c 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -28,6 +28,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; #if !(NET35 || NET20 || WINDOWS_PHONE || PORTABLE) +using System.ComponentModel; using System.Dynamic; #endif using System.Globalization; @@ -554,15 +555,26 @@ To fix this error either change the JSON to a {1} or change the deserialized typ if (contract.OnError != null && isTemporaryListReference) throw JsonSerializationException.Create(reader, "Cannot call OnError on an array or readonly list: {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); - PopulateList(arrayContract.CreateWrapper(list), reader, arrayContract, member, id); + if (!arrayContract.IsMultidimensionalArray) + PopulateList(arrayContract.CreateWrapper(list), reader, arrayContract, member, id); + else + PopulateMultidimensionalArray(list, reader, arrayContract, member, id); // create readonly and fixed sized collections using the temporary list if (isTemporaryListReference) { - if (contract.CreatedType.IsArray) - list = CollectionUtils.ToArray(((List)list).ToArray(), ReflectionUtils.GetCollectionItemType(contract.CreatedType)); + if (arrayContract.IsMultidimensionalArray) + { + list = CollectionUtils.ToMultidimensionalArray(list, ReflectionUtils.GetCollectionItemType(contract.CreatedType), contract.CreatedType.GetArrayRank()); + } + else if (contract.CreatedType.IsArray) + { + list = CollectionUtils.ToArray(((List) list).ToArray(), ReflectionUtils.GetCollectionItemType(contract.CreatedType)); + } else if (ReflectionUtils.InheritsGenericDefinition(contract.CreatedType, typeof(ReadOnlyCollection<>))) - list = (IList)ReflectionUtils.CreateInstance(contract.CreatedType, list); + { + list = (IList) ReflectionUtils.CreateInstance(contract.CreatedType, list); + } } else if (list is IWrappedCollection) { @@ -851,6 +863,125 @@ To fix this error either change the JSON to a {1} or change the deserialized typ throw JsonSerializationException.Create(reader, "Unexpected end when deserializing object."); } + private object PopulateMultidimensionalArray(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, string id) + { + int rank = contract.UnderlyingType.GetArrayRank(); + + if (id != null) + Serializer.ReferenceResolver.AddReference(this, id, list); + + contract.InvokeOnDeserializing(list, Serializer.Context); + + int initialDepth = reader.Depth; + + JsonContract collectionItemContract = GetContractSafe(contract.CollectionItemType); + JsonConverter collectionItemConverter = GetConverter(collectionItemContract, null, contract, containerProperty); + + int? previousErrorIndex = null; + Stack listStack = new Stack(); + listStack.Push(list); + IList currentList = list; + + while (true) + { + if (listStack.Count == rank) + { + try + { + if (ReadForType(reader, collectionItemContract, collectionItemConverter != null)) + { + switch (reader.TokenType) + { + case JsonToken.EndArray: + listStack.Pop(); + currentList = listStack.Peek(); + break; + case JsonToken.Comment: + break; + default: + object value; + + if (collectionItemConverter != null && collectionItemConverter.CanRead) + value = collectionItemConverter.ReadJson(reader, contract.CollectionItemType, null, GetInternalSerializer()); + else + value = CreateValueInternal(reader, contract.CollectionItemType, collectionItemContract, null, contract, containerProperty, null); + + currentList.Add(value); + break; + } + } + else + { + break; + } + } + catch (Exception ex) + { + JsonPosition errorPosition = reader.GetPosition(initialDepth); + + if (IsErrorHandled(list, contract, errorPosition.Position, reader.Path, ex)) + { + HandleError(reader, true, initialDepth); + + if (previousErrorIndex != null && previousErrorIndex == errorPosition.Position) + { + // reader index has not moved since previous error handling + // break out of reading array to prevent infinite loop + throw JsonSerializationException.Create(reader, "Infinite loop detected from error handling.", ex); + } + else + { + previousErrorIndex = errorPosition.Position; + } + } + else + { + throw; + } + } + } + else + { + if (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.StartArray: + IList newList = new List(); + currentList.Add(newList); + listStack.Push(newList); + currentList = newList; + break; + case JsonToken.EndArray: + listStack.Pop(); + + if (listStack.Count > 0) + { + currentList = listStack.Peek(); + } + else + { + contract.InvokeOnDeserialized(list, Serializer.Context); + + return list; + } + break; + case JsonToken.Comment: + break; + default: + throw JsonSerializationException.Create(reader, "Unexpected token when deserializing multidimensional array: " + reader.TokenType); + } + } + else + { + break; + } + } + } + + throw JsonSerializationException.Create(reader, "Unexpected end when deserializing array."); + } + private object PopulateList(IWrappedCollection wrappedList, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, string id) { object list = wrappedList.UnderlyingCollection; diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index 196a12f9..f940e81b 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -124,7 +124,10 @@ namespace Newtonsoft.Json.Serialization break; case JsonContractType.Array: JsonArrayContract arrayContract = (JsonArrayContract) valueContract; - SerializeList(writer, arrayContract.CreateWrapper(value), arrayContract, member, containerContract, containerProperty); + if (!arrayContract.IsMultidimensionalArray) + SerializeList(writer, arrayContract.CreateWrapper(value), arrayContract, member, containerContract, containerProperty); + else + SerializeMultidimensionalArray(writer, (Array)value, arrayContract, member, containerContract, containerProperty); break; case JsonContractType.Primitive: SerializePrimitive(writer, value, (JsonPrimitiveContract)valueContract, member, containerContract, containerProperty); @@ -322,39 +325,14 @@ namespace Newtonsoft.Json.Serialization { try { - if (!property.Ignored && property.Readable && ShouldSerialize(property, value) && IsSpecified(property, value)) - { - if (property.PropertyContract == null) - property.PropertyContract = Serializer.ContractResolver.ResolveContract(property.PropertyType); + object memberValue; + JsonContract memberContract; - object memberValue = property.ValueProvider.GetValue(value); - JsonContract memberContract = (property.PropertyContract.UnderlyingType.IsSealed()) ? property.PropertyContract : GetContractSafe(memberValue); + if (!CalculatePropertyValues(writer, value, contract, member, property, out memberContract, out memberValue)) + continue; - if (ShouldWriteProperty(memberValue, property)) - { - string propertyName = property.PropertyName; - - if (ShouldWriteReference(memberValue, property, memberContract, contract, member)) - { - writer.WritePropertyName(propertyName); - WriteReference(writer, memberValue); - continue; - } - - if (!CheckForCircularReference(writer, memberValue, property, memberContract, contract, member)) - continue; - - if (memberValue == null) - { - Required resolvedRequired = property._required ?? contract.ItemRequired ?? Required.Default; - if (resolvedRequired == Required.Always) - throw JsonSerializationException.Create(null, writer.ContainerPath, "Cannot write a null value for property '{0}'. Property requires a value.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName), null); - } - - writer.WritePropertyName(propertyName); - SerializeValue(writer, memberValue, memberContract, property, contract, member); - } - } + writer.WritePropertyName(property.PropertyName); + SerializeValue(writer, memberValue, memberContract, property, contract, member); } catch (Exception ex) { @@ -372,6 +350,45 @@ namespace Newtonsoft.Json.Serialization contract.InvokeOnSerialized(value, Serializer.Context); } + private bool CalculatePropertyValues(JsonWriter writer, object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, out JsonContract memberContract, out object memberValue) + { + if (!property.Ignored && property.Readable && ShouldSerialize(property, value) && IsSpecified(property, value)) + { + if (property.PropertyContract == null) + property.PropertyContract = Serializer.ContractResolver.ResolveContract(property.PropertyType); + + memberValue = property.ValueProvider.GetValue(value); + memberContract = (property.PropertyContract.UnderlyingType.IsSealed()) ? property.PropertyContract : GetContractSafe(memberValue); + + if (ShouldWriteProperty(memberValue, property)) + { + if (ShouldWriteReference(memberValue, property, memberContract, contract, member)) + { + writer.WritePropertyName(property.PropertyName); + WriteReference(writer, memberValue); + return false; + } + + if (!CheckForCircularReference(writer, memberValue, property, memberContract, contract, member)) + return false; + + if (memberValue == null) + { + JsonObjectContract objectContract = contract as JsonObjectContract; + Required resolvedRequired = property._required ?? ((objectContract != null) ? objectContract.ItemRequired : null) ?? Required.Default; + if (resolvedRequired == Required.Always) + throw JsonSerializationException.Create(null, writer.ContainerPath, "Cannot write a null value for property '{0}'. Property requires a value.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName), null); + } + + return true; + } + } + + memberContract = null; + memberValue = null; + return false; + } + private void WriteObjectStart(JsonWriter writer, object value, JsonContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) { writer.WriteStartObject(); @@ -434,7 +451,7 @@ namespace Newtonsoft.Json.Serialization _serializeStack.Add(values.UnderlyingCollection); - bool hasWrittenMetadataObject = WriteStartArray(writer, values, contract, member, collectionContract, containerProperty); + bool hasWrittenMetadataObject = WriteStartArray(writer, values.UnderlyingCollection, contract, member, collectionContract, containerProperty); writer.WriteStartArray(); @@ -483,7 +500,78 @@ namespace Newtonsoft.Json.Serialization contract.InvokeOnSerialized(values.UnderlyingCollection, Serializer.Context); } - private bool WriteStartArray(JsonWriter writer, IWrappedCollection values, JsonArrayContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty) + private void SerializeMultidimensionalArray(JsonWriter writer, Array values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) + { + contract.InvokeOnSerializing(values, Serializer.Context); + + _serializeStack.Add(values); + + bool hasWrittenMetadataObject = WriteStartArray(writer, values, contract, member, collectionContract, containerProperty); + + SerializeMultidimensionalArray(writer, values, contract, member, writer.Top, new int[0]); + + if (hasWrittenMetadataObject) + writer.WriteEndObject(); + + _serializeStack.RemoveAt(_serializeStack.Count - 1); + + contract.InvokeOnSerialized(values, Serializer.Context); + } + + private void SerializeMultidimensionalArray(JsonWriter writer, Array values, JsonArrayContract contract, JsonProperty member, int initialDepth, int[] indices) + { + int dimension = indices.Length; + int[] newIndices = new int[dimension + 1]; + for (int i = 0; i < dimension; i++) + { + newIndices[i] = indices[i]; + } + + writer.WriteStartArray(); + + for (int i = 0; i < values.GetLength(dimension); i++) + { + newIndices[dimension] = i; + bool isTopLevel = (newIndices.Length == values.Rank); + + if (isTopLevel) + { + object value = values.GetValue(newIndices); + + try + { + JsonContract valueContract = contract.FinalItemContract ?? GetContractSafe(values); + + if (ShouldWriteReference(value, null, valueContract, contract, member)) + { + WriteReference(writer, value); + } + else + { + if (CheckForCircularReference(writer, value, null, valueContract, contract, member)) + { + SerializeValue(writer, value, valueContract, null, contract, member); + } + } + } + catch (Exception ex) + { + if (IsErrorHandled(values, contract, i, writer.ContainerPath, ex)) + HandleError(writer, initialDepth); + else + throw; + } + } + else + { + SerializeMultidimensionalArray(writer, values, contract, member, initialDepth + 1, newIndices); + } + } + + writer.WriteEndArray(); + } + + private bool WriteStartArray(JsonWriter writer, object values, JsonArrayContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty) { bool isReference = ResolveIsReference(contract, member, containerContract, containerProperty) ?? HasFlag(Serializer.PreserveReferencesHandling, PreserveReferencesHandling.Arrays); bool includeTypeDetails = ShouldWriteType(TypeNameHandling.Arrays, contract, member, containerContract, containerProperty); @@ -496,11 +584,11 @@ namespace Newtonsoft.Json.Serialization if (isReference) { writer.WritePropertyName(JsonTypeReflector.IdPropertyName); - writer.WriteValue(Serializer.ReferenceResolver.GetReference(this, values.UnderlyingCollection)); + writer.WriteValue(Serializer.ReferenceResolver.GetReference(this, values)); } if (includeTypeDetails) { - WriteTypeProperty(writer, values.UnderlyingCollection.GetType()); + WriteTypeProperty(writer, values.GetType()); } writer.WritePropertyName(JsonTypeReflector.ArrayValuesPropertyName); } @@ -552,17 +640,55 @@ To fix this error either change the environment to be fully trusted, change the WriteObjectStart(writer, value, contract, member, collectionContract, containerProperty); + int initialDepth = writer.Top; + + foreach (JsonProperty property in contract.Properties) + { + // only write non-dynamic properties that have an explicit attribute + if (property.HasMemberAttribute) + { + try + { + object memberValue; + JsonContract memberContract; + + if (!CalculatePropertyValues(writer, value, contract, member, property, out memberContract, out memberValue)) + continue; + + writer.WritePropertyName(property.PropertyName); + SerializeValue(writer, memberValue, memberContract, property, contract, member); + } + catch (Exception ex) + { + if (IsErrorHandled(value, contract, property.PropertyName, writer.ContainerPath, ex)) + HandleError(writer, initialDepth); + else + throw; + } + } + } + foreach (string memberName in value.GetDynamicMemberNames()) { object memberValue; - if (DynamicUtils.TryGetMember(value, memberName, out memberValue)) + if (value.TryGetMember(memberName, out memberValue)) { - string resolvedPropertyName = (contract.PropertyNameResolver != null) - ? contract.PropertyNameResolver(memberName) - : memberName; + try + { + string resolvedPropertyName = (contract.PropertyNameResolver != null) + ? contract.PropertyNameResolver(memberName) + : memberName; - writer.WritePropertyName(resolvedPropertyName); - SerializeValue(writer, memberValue, GetContractSafe(memberValue), null, null, member); + writer.WritePropertyName(resolvedPropertyName); + SerializeValue(writer, memberValue, GetContractSafe(memberValue), null, null, member); + } + catch (Exception ex) + { + if (IsErrorHandled(value, contract, memberName, writer.ContainerPath, ex)) + HandleError(writer, initialDepth); + else + throw; + } } } diff --git a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs index 1d3ccf95..f31bc38e 100644 --- a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs @@ -326,5 +326,83 @@ namespace Newtonsoft.Json.Utilities } return -1; } + + private static IList GetDimensions(IList values) + { + IList dimensions = new List(); + + IList currentArray = values; + while (true) + { + dimensions.Add(currentArray.Count); + if (currentArray.Count == 0) + break; + + object v = currentArray[0]; + if (v is IList) + currentArray = (IList)v; + else + break; + } + + return dimensions; + } + + private static void CopyFromJaggedToMultidimensionalArray(IList values, Array multidimensionalArray, int[] indices) + { + int dimension = indices.Length; + if (dimension == multidimensionalArray.Rank) + { + multidimensionalArray.SetValue(JaggedArrayGetValue(values, indices), indices); + return; + } + + int dimensionLength = multidimensionalArray.GetLength(dimension); + IList list = (IList)JaggedArrayGetValue(values, indices); + int currentValuesLength = list.Count; + if (currentValuesLength != dimensionLength) + throw new Exception("Cannot deserialize non-cubical array as multidimensional array."); + + int[] newIndices = new int[dimension + 1]; + for (int i = 0; i < dimension; i++) + { + newIndices[i] = indices[i]; + } + + for (int i = 0; i < multidimensionalArray.GetLength(dimension); i++) + { + newIndices[dimension] = i; + CopyFromJaggedToMultidimensionalArray(values, multidimensionalArray, newIndices); + } + } + + private static object JaggedArrayGetValue(IList values, int[] indices) + { + IList currentList = values; + for (int i = 0; i < indices.Length; i++) + { + int index = indices[i]; + if (i == indices.Length - 1) + return currentList[index]; + else + currentList = (IList)currentList[index]; + } + return currentList; + } + + public static Array ToMultidimensionalArray(IList values, Type type, int rank) + { + IList dimensions = GetDimensions(values); + + while (dimensions.Count < rank) + { + dimensions.Add(0); + } + + Array multidimensionalArray = Array.CreateInstance(type, dimensions.ToArray()); + CopyFromJaggedToMultidimensionalArray(values, multidimensionalArray, new int[0]); + + return multidimensionalArray; + } } } \ No newline at end of file