From 04e9048d105164c34bf432e327fe622eae7566e5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 24 Nov 2014 22:01:53 +1300 Subject: [PATCH] -Refactor --- .../Properties/AssemblyInfo.cs | 2 +- .../Converters/StringEnumConverterTests.cs | 76 +++++++++++++ .../Linq/DynamicTests.cs | 49 +++++++++ Src/Newtonsoft.Json.Tests/Linq/JValueTests.cs | 15 +++ .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/JsonSerializerTest.cs | 18 ++++ .../Converters/StringEnumConverter.cs | 85 +-------------- .../Properties/AssemblyInfo.cs | 2 +- Src/Newtonsoft.Json/Utilities/EnumUtils.cs | 101 ++++++++++++++++-- 9 files changed, 259 insertions(+), 91 deletions(-) diff --git a/Src/Newtonsoft.Json.TestConsole/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.TestConsole/Properties/AssemblyInfo.cs index aa32efb8..75b99618 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.17911")] +[assembly: AssemblyFileVersion("6.0.6.17924")] diff --git a/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs index bcf7bd42..a84f3ad3 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs @@ -445,6 +445,14 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual(Bar.SerializeAsBaz, bars[2]); } + [Test] + public void DuplicateNameEnumTest() + { + ExceptionAssert.Throws( + () => JsonConvert.DeserializeObject("'foo_bar'", new StringEnumConverter()), + @"Error converting value ""foo_bar"" to type 'Newtonsoft.Json.Tests.Converters.DuplicateNameEnum'. Path '', line 1, position 9."); + } + // Define other methods and classes here [Flags] [JsonConverter(typeof(StringEnumConverter))] @@ -468,6 +476,74 @@ namespace Newtonsoft.Json.Tests.Converters [EnumMember(Value = "baz")] SerializeAsBaz } + + [Test] + public void DataContractSerializerDuplicateNameEnumTest() + { + MemoryStream ms = new MemoryStream(); + var s = new DataContractSerializer(typeof(DuplicateEnumNameTestClass)); + + ExceptionAssert.Throws(() => + { + s.WriteObject(ms, new DuplicateEnumNameTestClass + { + Value = DuplicateNameEnum.foo_bar, + Value2 = DuplicateNameEnum2.foo_bar_NOT_USED + }); + + Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray())); + + string xml = @" + foo_bar + foo_bar +"; + + Console.WriteLine(xml); + + var o = (DuplicateEnumNameTestClass)s.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(xml))); + + Assert.AreEqual(DuplicateNameEnum.foo_bar, o.Value); + Assert.AreEqual(DuplicateNameEnum2.FooBar, o.Value2); + + }, "Type 'Newtonsoft.Json.Tests.Converters.DuplicateNameEnum' contains two members 'foo_bar' 'and 'FooBar' with the same name 'foo_bar'. Multiple members with the same name in one type are not supported. Consider changing one of the member names using EnumMemberAttribute attribute."); + } #endif } + +#if !NET20 + [DataContract] + public class DuplicateEnumNameTestClass + { + [DataMember] + public DuplicateNameEnum Value { get; set; } + [DataMember] + public DuplicateNameEnum2 Value2 { get; set; } + } + + [DataContract] + public enum DuplicateNameEnum + { + [EnumMember] + first = 0, + [EnumMember] + foo_bar = 1, + [EnumMember(Value = "foo_bar")] + FooBar = 2, + [EnumMember] + foo_bar_NOT_USED = 3 + } + + [DataContract] + public enum DuplicateNameEnum2 + { + [EnumMember] + first = 0, + [EnumMember(Value = "foo_bar")] + FooBar = 1, + [EnumMember] + foo_bar = 2, + [EnumMember(Value = "TEST")] + foo_bar_NOT_USED = 3 + } +#endif } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Linq/DynamicTests.cs b/Src/Newtonsoft.Json.Tests/Linq/DynamicTests.cs index 0e10a125..3b47a37d 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/DynamicTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/DynamicTests.cs @@ -23,6 +23,7 @@ // OTHER DEALINGS IN THE SOFTWARE. #endregion +using System.Diagnostics; #if !(NET35 || NET20 || PORTABLE40) using System; using System.Collections.Generic; @@ -52,6 +53,54 @@ namespace Newtonsoft.Json.Tests.Linq [TestFixture] public class DynamicTests : TestFixtureBase { + private void UpdateValueCount(IDictionary counts, dynamic d) + { + string s = d.ToString(); + + int c; + if (!counts.TryGetValue(s, out c)) + { + c = 0; + } + + c++; + counts[s] = c; + } + + [Test] + public void DeserializeLargeDynamic() + { + dynamic d; + + using (var jsonFile = System.IO.File.OpenText("large.json")) + using (JsonTextReader jsonTextReader = new JsonTextReader(jsonFile)) + { + JsonSerializer serializer = new JsonSerializer(); + d = serializer.Deserialize(jsonTextReader); + } + + IDictionary counts = new Dictionary(); + + Stopwatch sw = new Stopwatch(); + sw.Start(); + + foreach (dynamic o in d) + { + foreach (dynamic friend in o.friends) + { + UpdateValueCount(counts, friend.id); + UpdateValueCount(counts, ((string) friend.name).Split(' ')[0]); + } + } + + //foreach (KeyValuePair keyValuePair in counts) + //{ + // Console.WriteLine(keyValuePair.Key + ": " + keyValuePair.Value); + //} + + Console.WriteLine("Time (secs): " + sw.Elapsed.TotalSeconds); + } + [Test] public void JObjectPropertyNames() { diff --git a/Src/Newtonsoft.Json.Tests/Linq/JValueTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JValueTests.cs index 9d45bd9b..fc7a554c 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JValueTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JValueTests.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.Serialization; using Newtonsoft.Json.Tests.TestObjects; #if !(NET20 || NET35 || PORTABLE || ASPNETCORE50) using System.Numerics; @@ -792,6 +793,20 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual(JTokenType.Integer, v.Type); StringComparison e6 = v.ToObject(); Assert.AreEqual(StringComparison.OrdinalIgnoreCase, e6); + + // does not support EnumMember. breaking change to add + ExceptionAssert.Throws(() => + { + d = new JValue("value_a"); + EnumA e7 = (EnumA)d; + Assert.AreEqual(EnumA.ValueA, e7); + }, "Requested value 'value_a' was not found."); + } + + public enum EnumA + { + [EnumMember(Value = "value_a")] + ValueA } #endif } diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index b22f60d7..f8367590 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.17911")] +[assembly: AssemblyFileVersion("6.0.6.17924")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index f3ba31f1..e3b04b39 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -95,6 +95,24 @@ namespace Newtonsoft.Json.Tests.Serialization [TestFixture] public class JsonSerializerTest : TestFixtureBase { +#if !NET20 + public enum EnumA + { + [EnumMember(Value = "value_a")] + ValueA + } + + [Test] + public void DeserializeEnumsByName() + { + var e1 = JsonConvert.DeserializeObject("'ValueA'"); + Assert.AreEqual(EnumA.ValueA, e1); + + var e2 = JsonConvert.DeserializeObject("'value_a'", new StringEnumConverter()); + Assert.AreEqual(EnumA.ValueA, e2); + } +#endif + #if !(NETFX_CORE || ASPNETCORE50 || NET20) [MetadataType(typeof(CustomerValidation))] public partial class CustomerWithMetadataType diff --git a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs index 70a397d3..a30304d1 100644 --- a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs +++ b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs @@ -33,7 +33,6 @@ using Newtonsoft.Json.Utilities; using Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; - #endif namespace Newtonsoft.Json.Converters @@ -43,8 +42,6 @@ namespace Newtonsoft.Json.Converters /// public class StringEnumConverter : JsonConverter { - private static readonly ThreadSafeStore> EnumMemberNamesPerType = new ThreadSafeStore>(InitializeEnumType); - /// /// Gets or sets a value indicating whether the written enum text should be camel case. /// @@ -90,24 +87,9 @@ namespace Newtonsoft.Json.Converters } else { - BidirectionalDictionary map = EnumMemberNamesPerType.Get(e.GetType()); + Type enumType = e.GetType(); - string[] names = enumName.Split(','); - for (int i = 0; i < names.Length; i++) - { - string name = names[i].Trim(); - - string resolvedEnumName; - map.TryGetByFirst(name, out resolvedEnumName); - resolvedEnumName = resolvedEnumName ?? name; - - if (CamelCaseText) - resolvedEnumName = StringUtils.ToCamelCase(resolvedEnumName); - - names[i] = resolvedEnumName; - } - - string finalName = string.Join(", ", names); + string finalName = EnumUtils.ToEnumName(enumType, enumName, CamelCaseText); writer.WriteValue(finalName); } @@ -139,30 +121,7 @@ namespace Newtonsoft.Json.Converters if (reader.TokenType == JsonToken.String) { string enumText = reader.Value.ToString(); - if (enumText == string.Empty && isNullable) - return null; - - string finalEnumText; - - BidirectionalDictionary map = EnumMemberNamesPerType.Get(t); - if (enumText.IndexOf(',') != -1) - { - string[] names = enumText.Split(','); - for (int i = 0; i < names.Length; i++) - { - string name = names[i].Trim(); - - names[i] = ResolvedEnumName(map, name); - } - - finalEnumText = string.Join(", ", names); - } - else - { - finalEnumText = ResolvedEnumName(map, enumText); - } - - return Enum.Parse(t, finalEnumText, true); + return EnumUtils.ParseEnumName(enumText, isNullable, t); } if (reader.TokenType == JsonToken.Integer) @@ -182,14 +141,6 @@ namespace Newtonsoft.Json.Converters throw JsonSerializationException.Create(reader, "Unexpected token {0} when parsing enum.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType)); } - private static string ResolvedEnumName(BidirectionalDictionary map, string enumText) - { - string resolvedEnumName; - map.TryGetBySecond(enumText, out resolvedEnumName); - resolvedEnumName = resolvedEnumName ?? enumText; - return resolvedEnumName; - } - /// /// Determines whether this instance can convert the specified object type. /// @@ -205,35 +156,5 @@ namespace Newtonsoft.Json.Converters return t.IsEnum(); } - - private static BidirectionalDictionary InitializeEnumType(Type type) - { - BidirectionalDictionary map = new BidirectionalDictionary( - StringComparer.OrdinalIgnoreCase, - StringComparer.OrdinalIgnoreCase); - - foreach (FieldInfo f in type.GetFields()) - { - string n1 = f.Name; - string n2; - -#if !NET20 - n2 = f.GetCustomAttributes(typeof(EnumMemberAttribute), true) - .Cast() - .Select(a => a.Value) - .SingleOrDefault() ?? f.Name; -#else - n2 = f.Name; -#endif - - string s; - if (map.TryGetBySecond(n2, out s)) - throw new InvalidOperationException("Enum name '{0}' already exists on enum '{1}'.".FormatWith(CultureInfo.InvariantCulture, n2, type.Name)); - - map.Set(n1, n2); - } - - return map; - } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index ae3623f9..dc57fc5b 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.17911")] +[assembly: AssemblyFileVersion("6.0.6.17924")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Utilities/EnumUtils.cs b/Src/Newtonsoft.Json/Utilities/EnumUtils.cs index 6837e32e..09b168e9 100644 --- a/Src/Newtonsoft.Json/Utilities/EnumUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/EnumUtils.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Runtime.Serialization; #if NET20 using Newtonsoft.Json.Utilities.LinqBridge; #else @@ -37,6 +38,38 @@ namespace Newtonsoft.Json.Utilities { internal static class EnumUtils { + private static readonly ThreadSafeStore> EnumMemberNamesPerType = new ThreadSafeStore>(InitializeEnumType); + + private static BidirectionalDictionary InitializeEnumType(Type type) + { + BidirectionalDictionary map = new BidirectionalDictionary( + StringComparer.OrdinalIgnoreCase, + StringComparer.OrdinalIgnoreCase); + + foreach (FieldInfo f in type.GetFields()) + { + string n1 = f.Name; + string n2; + +#if !NET20 + n2 = f.GetCustomAttributes(typeof(EnumMemberAttribute), true) + .Cast() + .Select(a => a.Value) + .SingleOrDefault() ?? f.Name; +#else + n2 = f.Name; +#endif + + string s; + if (map.TryGetBySecond(n2, out s)) + throw new InvalidOperationException("Enum name '{0}' already exists on enum '{1}'.".FormatWith(CultureInfo.InvariantCulture, n2, type.Name)); + + map.Set(n1, n2); + } + + return map; + } + public static IList GetFlagsValues(T value) where T : struct { Type enumType = typeof(T); @@ -112,9 +145,7 @@ namespace Newtonsoft.Json.Utilities List values = new List(); - var fields = from field in enumType.GetFields() - where field.IsLiteral - select field; + var fields = enumType.GetFields().Where(f => f.IsLiteral); foreach (FieldInfo field in fields) { @@ -132,9 +163,7 @@ namespace Newtonsoft.Json.Utilities List values = new List(); - var fields = from field in enumType.GetFields() - where field.IsLiteral - select field; + var fields = enumType.GetFields().Where(f => f.IsLiteral); foreach (FieldInfo field in fields) { @@ -143,5 +172,65 @@ namespace Newtonsoft.Json.Utilities return values; } + + public static object ParseEnumName(string enumText, bool isNullable, Type t) + { + if (enumText == string.Empty && isNullable) + return null; + + string finalEnumText; + + BidirectionalDictionary map = EnumMemberNamesPerType.Get(t); + if (enumText.IndexOf(',') != -1) + { + string[] names = enumText.Split(','); + for (int i = 0; i < names.Length; i++) + { + string name = names[i].Trim(); + + names[i] = ResolvedEnumName(map, name); + } + + finalEnumText = string.Join(", ", names); + } + else + { + finalEnumText = ResolvedEnumName(map, enumText); + } + + return Enum.Parse(t, finalEnumText, true); + } + + public static string ToEnumName(Type enumType, string enumText, bool camelCaseText) + { + BidirectionalDictionary map = EnumMemberNamesPerType.Get(enumType); + + string[] names = enumText.Split(','); + for (int i = 0; i < names.Length; i++) + { + string name = names[i].Trim(); + + string resolvedEnumName; + map.TryGetByFirst(name, out resolvedEnumName); + resolvedEnumName = resolvedEnumName ?? name; + + if (camelCaseText) + resolvedEnumName = StringUtils.ToCamelCase(resolvedEnumName); + + names[i] = resolvedEnumName; + } + + string finalName = string.Join(", ", names); + + return finalName; + } + + private static string ResolvedEnumName(BidirectionalDictionary map, string enumText) + { + string resolvedEnumName; + map.TryGetBySecond(enumText, out resolvedEnumName); + resolvedEnumName = resolvedEnumName ?? enumText; + return resolvedEnumName; + } } } \ No newline at end of file