From f2b6ea4966f2c99a38e07bb3b0d93f19db1da77b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 22 Feb 2018 15:15:55 +1300 Subject: [PATCH] -Fixed serializing generated types with duplicate property names --- .gitignore | 1 + Src/Newtonsoft.Json.Tests/App.config | 11 ++ Src/Newtonsoft.Json.Tests/Issues/Issue1620.cs | 105 ++++++++++++++++++ .../Newtonsoft.Json.Tests.csproj | 6 + .../Utilities/ReflectionUtils.cs | 9 +- .../Utilities/TypeExtensions.cs | 31 ++++-- 6 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 Src/Newtonsoft.Json.Tests/App.config create mode 100644 Src/Newtonsoft.Json.Tests/Issues/Issue1620.cs diff --git a/.gitignore b/.gitignore index 91f9896cc..810fdf33a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ _ReSharper.* *.ReSharper.user *.resharper.user .vs/ +.vscode/ *.lock.json *.nuget.props *.nuget.targets diff --git a/Src/Newtonsoft.Json.Tests/App.config b/Src/Newtonsoft.Json.Tests/App.config new file mode 100644 index 000000000..cf0b10390 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/App.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Issues/Issue1620.cs b/Src/Newtonsoft.Json.Tests/Issues/Issue1620.cs new file mode 100644 index 000000000..0101e813e --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Issues/Issue1620.cs @@ -0,0 +1,105 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +#if !(NET20 || NET35 || NET40) +using Moq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json.Utilities; +#if PORTABLE && !NETSTANDARD2_0 +using BindingFlags = Newtonsoft.Json.Utilities.BindingFlags; +#else +using BindingFlags = System.Reflection.BindingFlags; +#endif +#if DNXCORE50 +using Xunit; +using Test = Xunit.FactAttribute; +using Assert = Newtonsoft.Json.Tests.XUnitAssert; +#else +using NUnit.Framework; +#endif + +namespace Newtonsoft.Json.Tests.Issues +{ + [TestFixture] + public class Issue1620 : TestFixtureBase + { + [Test] + public void Test_SerializeMock() + { + Mock mock = new Mock(); + IFoo foo = mock.Object; + + string json = JsonConvert.SerializeObject(foo, new JsonSerializerSettings() { Converters = { new FooConverter() } }); + Assert.AreEqual(@"""foo""", json); + } + + [Test] + public void Test_GetFieldsAndProperties() + { + Mock mock = new Mock(); + IFoo foo = mock.Object; + + List properties = ReflectionUtils.GetFieldsAndProperties(foo.GetType(), BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).ToList(); + + Assert.AreEqual(1, properties.Count(p => p.Name == "Mock")); + } + + public interface IFoo + { + } + + public class Foo : IFoo + { + } + + public class FooConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue("foo"); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return new Foo(); + } + + public override bool CanConvert(Type objectType) + { + return typeof(IFoo).GetTypeInfo().IsAssignableFrom(objectType); + } + } + } +} +#endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index d95f8e870..779f469a2 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -55,6 +55,7 @@ + @@ -77,6 +78,7 @@ + @@ -99,6 +101,7 @@ + @@ -165,6 +168,7 @@ + @@ -185,6 +189,7 @@ + @@ -204,6 +209,7 @@ + diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index 3927b8eed..c9139c372 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -652,7 +652,7 @@ public static List GetFieldsAndProperties(Type type, BindingFlags bi } else { - IList resolvedMembers = new List(); + List resolvedMembers = new List(); foreach (MemberInfo memberInfo in groupedMember) { // this is a bit hacky @@ -664,6 +664,13 @@ public static List GetFieldsAndProperties(Type type, BindingFlags bi } else if (!IsOverridenGenericMember(memberInfo, bindingAttr) || memberInfo.Name == "Item") { + // two members with the same name were declared on a type + // this can be done via IL emit, e.g. Moq + if (resolvedMembers.Any(m => m.DeclaringType == memberInfo.DeclaringType)) + { + continue; + } + resolvedMembers.Add(memberInfo); } } diff --git a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs index 4b4ea3cbd..86b62a16e 100644 --- a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs +++ b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs @@ -363,20 +363,33 @@ public static IEnumerable GetProperties(this Type type, BindingFla return properties.Where(p => TestAccessibility(p, bindingFlags)); } + private static bool ContainsMemberName(IEnumerable members, string name) + { + foreach (MemberInfo memberInfo in members) + { + if (memberInfo.Name == name) + { + return true; + } + } + + return false; + } + private static IList GetMembersRecursive(this TypeInfo type) { TypeInfo t = type; - IList members = new List(); + List members = new List(); while (t != null) { foreach (MemberInfo member in t.DeclaredMembers) { - if (!members.Any(p => p.Name == member.Name)) + if (!ContainsMemberName(members, member.Name)) { members.Add(member); } } - t = (t.BaseType != null) ? t.BaseType.GetTypeInfo() : null; + t = t.BaseType?.GetTypeInfo(); } return members; @@ -385,17 +398,17 @@ private static IList GetMembersRecursive(this TypeInfo type) private static IList GetPropertiesRecursive(this TypeInfo type) { TypeInfo t = type; - IList properties = new List(); + List properties = new List(); while (t != null) { foreach (PropertyInfo member in t.DeclaredProperties) { - if (!properties.Any(p => p.Name == member.Name)) + if (!ContainsMemberName(properties, member.Name)) { properties.Add(member); } } - t = (t.BaseType != null) ? t.BaseType.GetTypeInfo() : null; + t = t.BaseType?.GetTypeInfo(); } return properties; @@ -404,17 +417,17 @@ private static IList GetPropertiesRecursive(this TypeInfo type) private static IList GetFieldsRecursive(this TypeInfo type) { TypeInfo t = type; - IList fields = new List(); + List fields = new List(); while (t != null) { foreach (FieldInfo member in t.DeclaredFields) { - if (!fields.Any(p => p.Name == member.Name)) + if (!ContainsMemberName(fields, member.Name)) { fields.Add(member); } } - t = (t.BaseType != null) ? t.BaseType.GetTypeInfo() : null; + t = t.BaseType?.GetTypeInfo(); } return fields;