From 2baedaa8780e92bae69ab141de376eda8ef559f4 Mon Sep 17 00:00:00 2001 From: Ilya Guskov Date: Wed, 21 Aug 2024 09:15:10 +0300 Subject: [PATCH 1/2] add insensetive case to enum as string --- .../Formatters/EnumAsStringFormatter`1.cs | 18 ++- .../Resolvers/DynamicEnumAsStringResolver.cs | 51 ++++++-- .../Tests/ShareTests/EnumAsStringTest.cs | 20 +-- tests/MessagePack.Tests/StringAsEnumTest.cs | 117 ++++++++++++++++++ 4 files changed, 178 insertions(+), 28 deletions(-) create mode 100644 tests/MessagePack.Tests/StringAsEnumTest.cs diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/EnumAsStringFormatter`1.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/EnumAsStringFormatter`1.cs index da237ed44..146d5eb71 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/EnumAsStringFormatter`1.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/EnumAsStringFormatter`1.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; +using MessagePack.Internal; namespace MessagePack.Formatters { @@ -12,18 +13,25 @@ namespace MessagePack.Formatters public sealed class EnumAsStringFormatter : IMessagePackFormatter where T : struct, Enum { + private readonly bool ignoreCase; private readonly IReadOnlyDictionary nameValueMapping; private readonly IReadOnlyDictionary valueNameMapping; private readonly IReadOnlyDictionary? clrToSerializationName; private readonly IReadOnlyDictionary? serializationToClrName; private readonly bool isFlags; + + public EnumAsStringFormatter(bool ignoreCase) + : this() + { + this.ignoreCase = ignoreCase; + } + public EnumAsStringFormatter() { this.isFlags = typeof(T).GetCustomAttribute() is object; - var fields = typeof(T).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static); - var nameValueMapping = new Dictionary(fields.Length); + var nameValueMapping = new Dictionary(fields.Length, ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); var valueNameMapping = new Dictionary(); Dictionary? clrToSerializationName = null; Dictionary? serializationToClrName = null; @@ -37,8 +45,8 @@ public EnumAsStringFormatter() var attribute = enumValueMember.GetCustomAttribute(); if (attribute is { IsValueSetExplicitly: true, Value: not null }) { - clrToSerializationName ??= new(); - serializationToClrName ??= new(); + clrToSerializationName ??= new(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); + serializationToClrName ??= new(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); clrToSerializationName.Add(name, attribute.Value); serializationToClrName.Add(attribute.Value, name); @@ -79,7 +87,7 @@ public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions // Avoid Enum.Parse when we can because it is too slow. if (!this.nameValueMapping.TryGetValue(name, out T value)) { - value = (T)Enum.Parse(typeof(T), this.GetClrNames(name)); + value = (T)Enum.Parse(typeof(T), this.GetClrNames(name), ignoreCase); } return value; diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs index b8610fa3c..2f56c713a 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.Reflection; using MessagePack.Formatters; using MessagePack.Internal; @@ -10,37 +11,61 @@ namespace MessagePack.Resolvers { public sealed class DynamicEnumAsStringResolver : IFormatterResolver { + private readonly bool ignoreCase; + private readonly Dictionary formatterCaches = new(); + /// - /// The singleton instance that can be used. + /// Case sensetive Instance + /// The singleton instance that can be used. + /// A instance with this formatter pre-configured. /// - public static readonly DynamicEnumAsStringResolver Instance; + public static readonly (DynamicEnumAsStringResolver Instance, MessagePackSerializerOptions Options) CaseSensetiveInstance; /// - /// A instance with this formatter pre-configured. + /// Case insensetive Instance + /// The singleton instance that can be used. + /// A instance with this formatter pre-configured. /// - public static readonly MessagePackSerializerOptions Options; + public static readonly (DynamicEnumAsStringResolver Instance, MessagePackSerializerOptions Options) CaseInsensitiveInstance; static DynamicEnumAsStringResolver() { - Instance = new DynamicEnumAsStringResolver(); - Options = new MessagePackSerializerOptions(Instance); + var instance = new DynamicEnumAsStringResolver(false); + CaseSensetiveInstance = (instance, new MessagePackSerializerOptions(instance)); + instance = new DynamicEnumAsStringResolver(true); + CaseInsensitiveInstance = (instance, new MessagePackSerializerOptions(instance)); } - private DynamicEnumAsStringResolver() + private DynamicEnumAsStringResolver(bool ignoreCase) { + this.ignoreCase = ignoreCase; } public IMessagePackFormatter? GetFormatter() { - return FormatterCache.Formatter; + if (formatterCaches.TryGetValue(typeof(T), out var formatter)) + { + return ((FormatterCache)formatter).Formatter; + } + + formatter = new FormatterCache(ignoreCase, + ignoreCase ? CaseInsensitiveInstance.Instance : CaseSensetiveInstance.Instance); + formatterCaches[typeof(T)] = formatter; + return ((FormatterCache)formatter).Formatter; + } + + private class FormatterCache + { } - private static class FormatterCache + private class FormatterCache : FormatterCache { - public static readonly IMessagePackFormatter? Formatter; + private readonly DynamicEnumAsStringResolver instance; + public readonly IMessagePackFormatter? Formatter; - static FormatterCache() + public FormatterCache(bool ignoreCase, DynamicEnumAsStringResolver instance) { + this.instance = instance; TypeInfo ti = typeof(T).GetTypeInfo(); if (ti.IsNullable()) @@ -52,7 +77,7 @@ static FormatterCache() return; } - var innerFormatter = DynamicEnumAsStringResolver.Instance.GetFormatterDynamic(ti.AsType()); + var innerFormatter = instance.GetFormatterDynamic(ti.AsType()); if (innerFormatter == null) { return; @@ -66,7 +91,7 @@ static FormatterCache() return; } - Formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(EnumAsStringFormatter<>).MakeGenericType(typeof(T)))!; + Formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(EnumAsStringFormatter<>).MakeGenericType(typeof(T)), new object[] { ignoreCase })!; } } } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringTest.cs index 2eb48b75a..605fc1963 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringTest.cs @@ -91,14 +91,14 @@ public class EnumAsStringTest new object[] { AsStringFlag.Foo, null, "Foo", "null" }, new object[] { AsStringFlag.Bar, AsStringFlag.Baz, "Bar", "Baz" }, new object[] { AsStringFlag.FooBar, AsStringFlag.FooBaz, "FooBar", "FooBaz" }, - new object[] { AsStringFlag.BarBaz, AsStringFlag.FooBarBaz, "BarBaz", "FooBarBaz" }, + new object[] { AsStringFlag.BarBaz, AsStringFlag.FooBarBaz, "BarBaz", "FooBarBaz", }, new object[] { AsStringFlag.Bar | AsStringFlag.FooBaz, AsStringFlag.BarBaz | AsStringFlag.FooBarBaz, "Bar, FooBaz", "BarBaz, FooBarBaz" }, new object[] { (AsStringFlag)10, (AsStringFlag)999, "Baz, FooBaz", "999" }, }; public static object[][] EnumDataForEnumMember = { - new object[] { AsStringWithEnumMember.Foo, null, "FooValue", "null" }, + new object[] { AsStringWithEnumMember.Foo, null, "FooValue", "null", }, new object[] { AsStringWithEnumMember.Bar, AsStringWithEnumMember.Baz, "BarValue", "BazValue" }, new object[] { AsStringWithEnumMember.FooBar, AsStringWithEnumMember.FooBaz, "FooBarValue", "FooBazValue" }, new object[] { AsStringWithEnumMember.BarBaz, AsStringWithEnumMember.FooBarBaz, "BarBazValue", "FooBarBazValue" }, @@ -120,13 +120,13 @@ public class EnumAsStringTest public void EnumTest(T x, T? y, string xName, string yName) where T : struct { - var bin = MessagePackSerializer.Serialize(x, DynamicEnumAsStringResolver.Options); + var bin = MessagePackSerializer.Serialize(x, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options); MessagePackSerializer.ConvertToJson(bin).Trim('\"').Is(xName); - MessagePackSerializer.Deserialize(bin, DynamicEnumAsStringResolver.Options).Is(x); + MessagePackSerializer.Deserialize(bin, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(x); - var bin2 = MessagePackSerializer.Serialize(y, DynamicEnumAsStringResolver.Options); + var bin2 = MessagePackSerializer.Serialize(y, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options); MessagePackSerializer.ConvertToJson(bin2).Trim('\"').Is(yName); - MessagePackSerializer.Deserialize(bin2, DynamicEnumAsStringResolver.Options).Is(y); + MessagePackSerializer.Deserialize(bin2, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(y); } [Theory] @@ -134,13 +134,13 @@ public void EnumTest(T x, T? y, string xName, string yName) public void EnumTestEnumMember(T x, T? y, string xName, string yName) where T : struct { - var bin = MessagePackSerializer.Serialize(x, DynamicEnumAsStringResolver.Options); + var bin = MessagePackSerializer.Serialize(x, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options); MessagePackSerializer.ConvertToJson(bin).Trim('\"').Is(xName); - MessagePackSerializer.Deserialize(bin, DynamicEnumAsStringResolver.Options).Is(x); + MessagePackSerializer.Deserialize(bin, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(x); - var bin2 = MessagePackSerializer.Serialize(y, DynamicEnumAsStringResolver.Options); + var bin2 = MessagePackSerializer.Serialize(y, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options); MessagePackSerializer.ConvertToJson(bin2).Trim('\"').Is(yName); - MessagePackSerializer.Deserialize(bin2, DynamicEnumAsStringResolver.Options).Is(y); + MessagePackSerializer.Deserialize(bin2, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(y); } } diff --git a/tests/MessagePack.Tests/StringAsEnumTest.cs b/tests/MessagePack.Tests/StringAsEnumTest.cs new file mode 100644 index 000000000..e6ea9fbf8 --- /dev/null +++ b/tests/MessagePack.Tests/StringAsEnumTest.cs @@ -0,0 +1,117 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if !(MESSAGEPACK_FORCE_AOT || ENABLE_IL2CPP) +#define DYNAMIC_GENERATION +#endif + +using System; +using System.Runtime.Serialization; +using MessagePack.Resolvers; +using Xunit; + +namespace MessagePack.Tests +{ +#if DYNAMIC_GENERATION + + public class StringAsEnumTest + { + public static object[][] EnumData = new object[][] + { + // simple ignore case + new object[] { AsString.Foo, null, "Foo", null, true }, + new object[] { AsString.Bar, AsString.Baz, "BAr", "BAz", true }, + new object[] { AsString.FooBar, AsString.FooBaz, "FooBAr", "FooBAz", true }, + new object[] { AsString.BarBaz, AsString.FooBarBaz, "BarBAz", "FooBarBAz", true }, + new object[] { (AsString)10, (AsString)999, "10", "999", true }, + new object[] { (AsStringWithEnumMember)10, (AsStringWithEnumMember)999, "10", "999", true }, + + // flags ignore case + new object[] { AsStringFlag.Foo, null, "Foo", null, true }, + new object[] { AsStringFlag.Bar, AsStringFlag.Baz, "BAr", "BAz", true }, + new object[] { AsStringFlag.FooBar, AsStringFlag.FooBaz, "FooBAr", "FooBAz", true }, + new object[] { AsStringFlag.BarBaz, AsStringFlag.FooBarBaz, "BarBAz", "FooBarBAz", true }, + new object[] { AsStringFlag.Bar | AsStringFlag.FooBaz, AsStringFlag.BarBaz | AsStringFlag.FooBarBaz, "Bar, FooBAz", "BarBAz, FooBarBAz", true }, + new object[] { (AsStringFlag)10, (AsStringFlag)999, "BAz, FooBAz", "999", true }, + + // simple case sensetive + new object[] { AsString.Foo, null, "Foo", null , false }, + new object[] { AsString.Bar, AsString.Baz, "Bar", "Baz", false }, + new object[] { AsString.FooBar, AsString.FooBaz, "FooBar", "FooBaz", false }, + new object[] { AsString.BarBaz, AsString.FooBarBaz, "BarBaz", "FooBarBaz", false }, + new object[] { (AsString)10, (AsString)999, "10", "999", false }, + new object[] { (AsStringWithEnumMember)10, (AsStringWithEnumMember)999, "10", "999", false }, + + // flags case sensetive + new object[] { AsStringFlag.Foo, null, "Foo", null , false }, + new object[] { AsStringFlag.Bar, AsStringFlag.Baz, "Bar", "Baz", false }, + new object[] { AsStringFlag.FooBar, AsStringFlag.FooBaz, "FooBar", "FooBaz", false }, + new object[] { AsStringFlag.BarBaz, AsStringFlag.FooBarBaz, "BarBaz", "FooBarBaz", false }, + new object[] { AsStringFlag.Bar | AsStringFlag.FooBaz, AsStringFlag.BarBaz | AsStringFlag.FooBarBaz, "Bar, FooBaz", "BarBaz, FooBarBaz", false }, + new object[] { (AsStringFlag)10, (AsStringFlag)999, "Baz, FooBaz", "999", false }, + }; + + public static object[][] EnumDataForEnumMember = + { + //ignore case + new object[] { AsStringWithEnumMember.Foo, null, "FooVAlue", null, true }, + new object[] { AsStringWithEnumMember.Bar, AsStringWithEnumMember.Baz, "BarVAlue", "BazVAlue", true }, + new object[] { AsStringWithEnumMember.FooBar, AsStringWithEnumMember.FooBaz, "FooBArValue", "FooBAzValue", true }, + new object[] { AsStringWithEnumMember.BarBaz, AsStringWithEnumMember.FooBarBaz, "BarBAzValue", "FooBarBAzValue", true }, + new object[] { (AsStringWithEnumMember)10, (AsStringWithEnumMember)999, "10", "999", true }, + new object[] { (AsStringWithEnumMember)10, (AsStringWithEnumMember)999, "10", "999", true }, + new object[] { AsStringWithEnumMember.FooBarBazOther, AsStringWithEnumMember.FooBarBazOther, "FooBarBAzOther", "FooBArBazOther", true }, + + // flags (for flags enumMember is partially supported) ignore case + new object[] { AsStringFlagWithEnumMember.Foo, null, "FooValue", null, true }, + new object[] { AsStringFlagWithEnumMember.Bar, AsStringFlagWithEnumMember.Baz, "BArValue", "BAzValue", true }, + new object[] { AsStringFlagWithEnumMember.FooBar, AsStringFlagWithEnumMember.FooBaz, "FooBArValue", "FooBAzValue", true }, + new object[] { AsStringFlagWithEnumMember.BarBaz, AsStringFlagWithEnumMember.FooBarBaz, "BarBAzValue", "FooBArBazValue", true }, + new object[] { AsStringFlagWithEnumMember.Bar | AsStringFlagWithEnumMember.FooBaz, AsStringFlagWithEnumMember.BarBaz | AsStringFlagWithEnumMember.FooBarBaz, "BArValue, FooBAzValue", "BarBAzValue, FooBarBAzValue", true }, + new object[] { (AsStringFlagWithEnumMember)10, (AsStringFlagWithEnumMember)999, "BAzValue, FooBAzValue", "999", true }, + + // insesetive + new object[] { AsStringWithEnumMember.Foo, null, "FooValue", null, false }, + new object[] { AsStringWithEnumMember.Bar, AsStringWithEnumMember.Baz, "BarValue", "BazValue", false }, + new object[] { AsStringWithEnumMember.FooBar, AsStringWithEnumMember.FooBaz, "FooBarValue", "FooBazValue", false }, + new object[] { AsStringWithEnumMember.BarBaz, AsStringWithEnumMember.FooBarBaz, "BarBazValue", "FooBarBazValue", false }, + new object[] { (AsStringWithEnumMember)10, (AsStringWithEnumMember)999, "10", "999", false }, + new object[] { (AsStringWithEnumMember)10, (AsStringWithEnumMember)999, "10", "999", false }, + new object[] { AsStringWithEnumMember.FooBarBazOther, AsStringWithEnumMember.FooBarBazOther, "FooBarBazOther", "FooBarBazOther", false }, + + // flags (for flags enumMember is partially supported) case insesetive + new object[] { AsStringFlagWithEnumMember.Foo, null, "FooValue", null, false }, + new object[] { AsStringFlagWithEnumMember.Bar, AsStringFlagWithEnumMember.Baz, "BarValue", "BazValue", false }, + new object[] { AsStringFlagWithEnumMember.FooBar, AsStringFlagWithEnumMember.FooBaz, "FooBarValue", "FooBazValue", false }, + new object[] { AsStringFlagWithEnumMember.BarBaz, AsStringFlagWithEnumMember.FooBarBaz, "BarBazValue", "FooBarBazValue", false }, + new object[] { AsStringFlagWithEnumMember.Bar | AsStringFlagWithEnumMember.FooBaz, AsStringFlagWithEnumMember.BarBaz | AsStringFlagWithEnumMember.FooBarBaz, "BarValue, FooBazValue", "BarBazValue, FooBarBazValue", false }, + new object[] { (AsStringFlagWithEnumMember)10, (AsStringFlagWithEnumMember)999, "BazValue, FooBazValue", "999", false }, + }; + + [Theory] + [MemberData(nameof(EnumData))] + public void EnumTest(T x, T? y, string xName, string yName, bool ignoreCase) + where T : struct + { + var bin = MessagePackSerializer.Serialize(xName, StandardResolver.Options); + MessagePackSerializer.Deserialize(bin, ignoreCase ? DynamicEnumAsStringResolver.CaseInsensitiveInstance.Options : DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(x); + + var bin2 = MessagePackSerializer.Serialize(yName, StandardResolver.Options); + MessagePackSerializer.Deserialize(bin2, ignoreCase ? DynamicEnumAsStringResolver.CaseInsensitiveInstance.Options : DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(y); + } + + [Theory] + [MemberData(nameof(EnumDataForEnumMember))] + public void EnumTestEnumMember(T x, T? y, string xName, string yName, bool ignoreCase) + where T : struct + { + var bin = MessagePackSerializer.Serialize(xName, StandardResolver.Options); + MessagePackSerializer.Deserialize(bin, ignoreCase ? DynamicEnumAsStringResolver.CaseInsensitiveInstance.Options : DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(x); + + var bin2 = MessagePackSerializer.Serialize(yName, StandardResolver.Options); + MessagePackSerializer.Deserialize(bin2, ignoreCase ? DynamicEnumAsStringResolver.CaseInsensitiveInstance.Options : DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(y); + } + } + +#endif +} From 184ffc47037d492f9bf1d6e97a92eaa87049b193 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 22 Aug 2024 07:06:06 -0600 Subject: [PATCH 2/2] Fix faulty formatter, style and revert unnecessary changes --- .../Formatters/EnumAsStringFormatter`1.cs | 23 +++++-- .../DynamicEnumAsStringIgnoreCaseResolver.cs | 64 +++++++++++++++++++ ...amicEnumAsStringIgnoreCaseResolver.cs.meta | 11 ++++ .../Resolvers/DynamicEnumAsStringResolver.cs | 51 ++++----------- .../ShareTests/EnumAsStringIgnoreCaseTests.cs | 25 +++++--- .../EnumAsStringIgnoreCaseTests.cs.meta | 11 ++++ .../Tests/ShareTests/EnumAsStringTest.cs | 20 +++--- .../net472/PublicAPI.Unshipped.txt | 4 ++ .../net6.0/PublicAPI.Unshipped.txt | 4 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 4 ++ 10 files changed, 152 insertions(+), 65 deletions(-) create mode 100644 src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs create mode 100644 src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs.meta rename tests/MessagePack.Tests/StringAsEnumTest.cs => src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringIgnoreCaseTests.cs (90%) create mode 100644 src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringIgnoreCaseTests.cs.meta diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/EnumAsStringFormatter`1.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/EnumAsStringFormatter`1.cs index 146d5eb71..681439f9a 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/EnumAsStringFormatter`1.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/EnumAsStringFormatter`1.cs @@ -20,15 +20,24 @@ public sealed class EnumAsStringFormatter : IMessagePackFormatter private readonly IReadOnlyDictionary? serializationToClrName; private readonly bool isFlags; + /// + /// Initializes a new instance of the class + /// that is case sensitive. + /// + public EnumAsStringFormatter() + : this(ignoreCase: false) + { + } + /// + /// Initializes a new instance of the class. + /// + /// A value indicating whether to allow enum value names to mismatch on deserialization. public EnumAsStringFormatter(bool ignoreCase) - : this() { this.ignoreCase = ignoreCase; - } + StringComparer stringComparer = ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - public EnumAsStringFormatter() - { this.isFlags = typeof(T).GetCustomAttribute() is object; var fields = typeof(T).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static); var nameValueMapping = new Dictionary(fields.Length, ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); @@ -45,8 +54,8 @@ public EnumAsStringFormatter() var attribute = enumValueMember.GetCustomAttribute(); if (attribute is { IsValueSetExplicitly: true, Value: not null }) { - clrToSerializationName ??= new(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); - serializationToClrName ??= new(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); + clrToSerializationName ??= new(stringComparer); + serializationToClrName ??= new(stringComparer); clrToSerializationName.Add(name, attribute.Value); serializationToClrName.Add(attribute.Value, name); @@ -87,7 +96,7 @@ public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions // Avoid Enum.Parse when we can because it is too slow. if (!this.nameValueMapping.TryGetValue(name, out T value)) { - value = (T)Enum.Parse(typeof(T), this.GetClrNames(name), ignoreCase); + value = (T)Enum.Parse(typeof(T), this.GetClrNames(name), this.ignoreCase); } return value; diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs new file mode 100644 index 000000000..afa565d02 --- /dev/null +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs @@ -0,0 +1,64 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Reflection; +using MessagePack.Formatters; +using MessagePack.Internal; + +namespace MessagePack.Resolvers +{ + public sealed class DynamicEnumAsStringIgnoreCaseResolver : IFormatterResolver + { + /// + /// The singleton instance that can be used. + /// + public static readonly DynamicEnumAsStringIgnoreCaseResolver Instance = new(); + + private DynamicEnumAsStringIgnoreCaseResolver() + { + } + + public IMessagePackFormatter? GetFormatter() + { + return FormatterCache.Formatter; + } + + private static class FormatterCache + { + private static readonly object?[] FormatterCtorArgs = new object?[] { true }; + + public static readonly IMessagePackFormatter? Formatter; + + static FormatterCache() + { + TypeInfo ti = typeof(T).GetTypeInfo(); + + if (ti.IsNullable()) + { + // build underlying type and use wrapped formatter. + ti = ti.GenericTypeArguments[0].GetTypeInfo(); + if (!ti.IsEnum) + { + return; + } + + var innerFormatter = Instance.GetFormatterDynamic(ti.AsType()); + if (innerFormatter == null) + { + return; + } + + Formatter = (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(ti.AsType()), new object[] { innerFormatter }); + return; + } + else if (!ti.IsEnum) + { + return; + } + + Formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(EnumAsStringFormatter<>).MakeGenericType(typeof(T)), FormatterCtorArgs)!; + } + } + } +} diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs.meta b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs.meta new file mode 100644 index 000000000..90cc8304e --- /dev/null +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 37249e0e742d459c94cb2c8cbc1e0d79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs index 2f56c713a..b8610fa3c 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections.Generic; using System.Reflection; using MessagePack.Formatters; using MessagePack.Internal; @@ -11,61 +10,37 @@ namespace MessagePack.Resolvers { public sealed class DynamicEnumAsStringResolver : IFormatterResolver { - private readonly bool ignoreCase; - private readonly Dictionary formatterCaches = new(); - /// - /// Case sensetive Instance - /// The singleton instance that can be used. - /// A instance with this formatter pre-configured. + /// The singleton instance that can be used. /// - public static readonly (DynamicEnumAsStringResolver Instance, MessagePackSerializerOptions Options) CaseSensetiveInstance; + public static readonly DynamicEnumAsStringResolver Instance; /// - /// Case insensetive Instance - /// The singleton instance that can be used. - /// A instance with this formatter pre-configured. + /// A instance with this formatter pre-configured. /// - public static readonly (DynamicEnumAsStringResolver Instance, MessagePackSerializerOptions Options) CaseInsensitiveInstance; + public static readonly MessagePackSerializerOptions Options; static DynamicEnumAsStringResolver() { - var instance = new DynamicEnumAsStringResolver(false); - CaseSensetiveInstance = (instance, new MessagePackSerializerOptions(instance)); - instance = new DynamicEnumAsStringResolver(true); - CaseInsensitiveInstance = (instance, new MessagePackSerializerOptions(instance)); + Instance = new DynamicEnumAsStringResolver(); + Options = new MessagePackSerializerOptions(Instance); } - private DynamicEnumAsStringResolver(bool ignoreCase) + private DynamicEnumAsStringResolver() { - this.ignoreCase = ignoreCase; } public IMessagePackFormatter? GetFormatter() { - if (formatterCaches.TryGetValue(typeof(T), out var formatter)) - { - return ((FormatterCache)formatter).Formatter; - } - - formatter = new FormatterCache(ignoreCase, - ignoreCase ? CaseInsensitiveInstance.Instance : CaseSensetiveInstance.Instance); - formatterCaches[typeof(T)] = formatter; - return ((FormatterCache)formatter).Formatter; - } - - private class FormatterCache - { + return FormatterCache.Formatter; } - private class FormatterCache : FormatterCache + private static class FormatterCache { - private readonly DynamicEnumAsStringResolver instance; - public readonly IMessagePackFormatter? Formatter; + public static readonly IMessagePackFormatter? Formatter; - public FormatterCache(bool ignoreCase, DynamicEnumAsStringResolver instance) + static FormatterCache() { - this.instance = instance; TypeInfo ti = typeof(T).GetTypeInfo(); if (ti.IsNullable()) @@ -77,7 +52,7 @@ public FormatterCache(bool ignoreCase, DynamicEnumAsStringResolver instance) return; } - var innerFormatter = instance.GetFormatterDynamic(ti.AsType()); + var innerFormatter = DynamicEnumAsStringResolver.Instance.GetFormatterDynamic(ti.AsType()); if (innerFormatter == null) { return; @@ -91,7 +66,7 @@ public FormatterCache(bool ignoreCase, DynamicEnumAsStringResolver instance) return; } - Formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(EnumAsStringFormatter<>).MakeGenericType(typeof(T)), new object[] { ignoreCase })!; + Formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(EnumAsStringFormatter<>).MakeGenericType(typeof(T)))!; } } } diff --git a/tests/MessagePack.Tests/StringAsEnumTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringIgnoreCaseTests.cs similarity index 90% rename from tests/MessagePack.Tests/StringAsEnumTest.cs rename to src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringIgnoreCaseTests.cs index e6ea9fbf8..08e351463 100644 --- a/tests/MessagePack.Tests/StringAsEnumTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringIgnoreCaseTests.cs @@ -14,8 +14,13 @@ namespace MessagePack.Tests { #if DYNAMIC_GENERATION - public class StringAsEnumTest + public class EnumAsStringIgnoreCaseTests { + private static readonly MessagePackSerializerOptions IgnoreCaseOptions = MessagePackSerializerOptions.Standard + .WithResolver(DynamicEnumAsStringIgnoreCaseResolver.Instance); + + private static readonly MessagePackSerializerOptions CaseSensitiveOptions = DynamicEnumAsStringResolver.Options; + public static object[][] EnumData = new object[][] { // simple ignore case @@ -35,7 +40,7 @@ public class StringAsEnumTest new object[] { (AsStringFlag)10, (AsStringFlag)999, "BAz, FooBAz", "999", true }, // simple case sensetive - new object[] { AsString.Foo, null, "Foo", null , false }, + new object[] { AsString.Foo, null, "Foo", null, false }, new object[] { AsString.Bar, AsString.Baz, "Bar", "Baz", false }, new object[] { AsString.FooBar, AsString.FooBaz, "FooBar", "FooBaz", false }, new object[] { AsString.BarBaz, AsString.FooBarBaz, "BarBaz", "FooBarBaz", false }, @@ -43,7 +48,7 @@ public class StringAsEnumTest new object[] { (AsStringWithEnumMember)10, (AsStringWithEnumMember)999, "10", "999", false }, // flags case sensetive - new object[] { AsStringFlag.Foo, null, "Foo", null , false }, + new object[] { AsStringFlag.Foo, null, "Foo", null, false }, new object[] { AsStringFlag.Bar, AsStringFlag.Baz, "Bar", "Baz", false }, new object[] { AsStringFlag.FooBar, AsStringFlag.FooBaz, "FooBar", "FooBaz", false }, new object[] { AsStringFlag.BarBaz, AsStringFlag.FooBarBaz, "BarBaz", "FooBarBaz", false }, @@ -53,7 +58,7 @@ public class StringAsEnumTest public static object[][] EnumDataForEnumMember = { - //ignore case + // ignore case new object[] { AsStringWithEnumMember.Foo, null, "FooVAlue", null, true }, new object[] { AsStringWithEnumMember.Bar, AsStringWithEnumMember.Baz, "BarVAlue", "BazVAlue", true }, new object[] { AsStringWithEnumMember.FooBar, AsStringWithEnumMember.FooBaz, "FooBArValue", "FooBAzValue", true }, @@ -70,7 +75,7 @@ public class StringAsEnumTest new object[] { AsStringFlagWithEnumMember.Bar | AsStringFlagWithEnumMember.FooBaz, AsStringFlagWithEnumMember.BarBaz | AsStringFlagWithEnumMember.FooBarBaz, "BArValue, FooBAzValue", "BarBAzValue, FooBarBAzValue", true }, new object[] { (AsStringFlagWithEnumMember)10, (AsStringFlagWithEnumMember)999, "BAzValue, FooBAzValue", "999", true }, - // insesetive + // case sensitive new object[] { AsStringWithEnumMember.Foo, null, "FooValue", null, false }, new object[] { AsStringWithEnumMember.Bar, AsStringWithEnumMember.Baz, "BarValue", "BazValue", false }, new object[] { AsStringWithEnumMember.FooBar, AsStringWithEnumMember.FooBaz, "FooBarValue", "FooBazValue", false }, @@ -79,7 +84,7 @@ public class StringAsEnumTest new object[] { (AsStringWithEnumMember)10, (AsStringWithEnumMember)999, "10", "999", false }, new object[] { AsStringWithEnumMember.FooBarBazOther, AsStringWithEnumMember.FooBarBazOther, "FooBarBazOther", "FooBarBazOther", false }, - // flags (for flags enumMember is partially supported) case insesetive + // flags (for flags enumMember is partially supported) case sensitive new object[] { AsStringFlagWithEnumMember.Foo, null, "FooValue", null, false }, new object[] { AsStringFlagWithEnumMember.Bar, AsStringFlagWithEnumMember.Baz, "BarValue", "BazValue", false }, new object[] { AsStringFlagWithEnumMember.FooBar, AsStringFlagWithEnumMember.FooBaz, "FooBarValue", "FooBazValue", false }, @@ -94,10 +99,10 @@ public void EnumTest(T x, T? y, string xName, string yName, bool ignoreCase) where T : struct { var bin = MessagePackSerializer.Serialize(xName, StandardResolver.Options); - MessagePackSerializer.Deserialize(bin, ignoreCase ? DynamicEnumAsStringResolver.CaseInsensitiveInstance.Options : DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(x); + MessagePackSerializer.Deserialize(bin, ignoreCase ? IgnoreCaseOptions : CaseSensitiveOptions).Is(x); var bin2 = MessagePackSerializer.Serialize(yName, StandardResolver.Options); - MessagePackSerializer.Deserialize(bin2, ignoreCase ? DynamicEnumAsStringResolver.CaseInsensitiveInstance.Options : DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(y); + MessagePackSerializer.Deserialize(bin2, ignoreCase ? IgnoreCaseOptions : CaseSensitiveOptions).Is(y); } [Theory] @@ -106,10 +111,10 @@ public void EnumTestEnumMember(T x, T? y, string xName, string yName, bool ig where T : struct { var bin = MessagePackSerializer.Serialize(xName, StandardResolver.Options); - MessagePackSerializer.Deserialize(bin, ignoreCase ? DynamicEnumAsStringResolver.CaseInsensitiveInstance.Options : DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(x); + MessagePackSerializer.Deserialize(bin, ignoreCase ? IgnoreCaseOptions : CaseSensitiveOptions).Is(x); var bin2 = MessagePackSerializer.Serialize(yName, StandardResolver.Options); - MessagePackSerializer.Deserialize(bin2, ignoreCase ? DynamicEnumAsStringResolver.CaseInsensitiveInstance.Options : DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(y); + MessagePackSerializer.Deserialize(bin2, ignoreCase ? IgnoreCaseOptions : CaseSensitiveOptions).Is(y); } } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringIgnoreCaseTests.cs.meta b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringIgnoreCaseTests.cs.meta new file mode 100644 index 000000000..7925ac8ca --- /dev/null +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringIgnoreCaseTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93bfd47f7c8e4b02b8b49a66041bf1b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringTest.cs index 605fc1963..2eb48b75a 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/EnumAsStringTest.cs @@ -91,14 +91,14 @@ public class EnumAsStringTest new object[] { AsStringFlag.Foo, null, "Foo", "null" }, new object[] { AsStringFlag.Bar, AsStringFlag.Baz, "Bar", "Baz" }, new object[] { AsStringFlag.FooBar, AsStringFlag.FooBaz, "FooBar", "FooBaz" }, - new object[] { AsStringFlag.BarBaz, AsStringFlag.FooBarBaz, "BarBaz", "FooBarBaz", }, + new object[] { AsStringFlag.BarBaz, AsStringFlag.FooBarBaz, "BarBaz", "FooBarBaz" }, new object[] { AsStringFlag.Bar | AsStringFlag.FooBaz, AsStringFlag.BarBaz | AsStringFlag.FooBarBaz, "Bar, FooBaz", "BarBaz, FooBarBaz" }, new object[] { (AsStringFlag)10, (AsStringFlag)999, "Baz, FooBaz", "999" }, }; public static object[][] EnumDataForEnumMember = { - new object[] { AsStringWithEnumMember.Foo, null, "FooValue", "null", }, + new object[] { AsStringWithEnumMember.Foo, null, "FooValue", "null" }, new object[] { AsStringWithEnumMember.Bar, AsStringWithEnumMember.Baz, "BarValue", "BazValue" }, new object[] { AsStringWithEnumMember.FooBar, AsStringWithEnumMember.FooBaz, "FooBarValue", "FooBazValue" }, new object[] { AsStringWithEnumMember.BarBaz, AsStringWithEnumMember.FooBarBaz, "BarBazValue", "FooBarBazValue" }, @@ -120,13 +120,13 @@ public class EnumAsStringTest public void EnumTest(T x, T? y, string xName, string yName) where T : struct { - var bin = MessagePackSerializer.Serialize(x, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options); + var bin = MessagePackSerializer.Serialize(x, DynamicEnumAsStringResolver.Options); MessagePackSerializer.ConvertToJson(bin).Trim('\"').Is(xName); - MessagePackSerializer.Deserialize(bin, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(x); + MessagePackSerializer.Deserialize(bin, DynamicEnumAsStringResolver.Options).Is(x); - var bin2 = MessagePackSerializer.Serialize(y, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options); + var bin2 = MessagePackSerializer.Serialize(y, DynamicEnumAsStringResolver.Options); MessagePackSerializer.ConvertToJson(bin2).Trim('\"').Is(yName); - MessagePackSerializer.Deserialize(bin2, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(y); + MessagePackSerializer.Deserialize(bin2, DynamicEnumAsStringResolver.Options).Is(y); } [Theory] @@ -134,13 +134,13 @@ public void EnumTest(T x, T? y, string xName, string yName) public void EnumTestEnumMember(T x, T? y, string xName, string yName) where T : struct { - var bin = MessagePackSerializer.Serialize(x, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options); + var bin = MessagePackSerializer.Serialize(x, DynamicEnumAsStringResolver.Options); MessagePackSerializer.ConvertToJson(bin).Trim('\"').Is(xName); - MessagePackSerializer.Deserialize(bin, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(x); + MessagePackSerializer.Deserialize(bin, DynamicEnumAsStringResolver.Options).Is(x); - var bin2 = MessagePackSerializer.Serialize(y, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options); + var bin2 = MessagePackSerializer.Serialize(y, DynamicEnumAsStringResolver.Options); MessagePackSerializer.ConvertToJson(bin2).Trim('\"').Is(yName); - MessagePackSerializer.Deserialize(bin2, DynamicEnumAsStringResolver.CaseSensetiveInstance.Options).Is(y); + MessagePackSerializer.Deserialize(bin2, DynamicEnumAsStringResolver.Options).Is(y); } } diff --git a/src/MessagePack/net472/PublicAPI.Unshipped.txt b/src/MessagePack/net472/PublicAPI.Unshipped.txt index e69de29bb..8b7f92b5a 100644 --- a/src/MessagePack/net472/PublicAPI.Unshipped.txt +++ b/src/MessagePack/net472/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +MessagePack.Formatters.EnumAsStringFormatter.EnumAsStringFormatter(bool ignoreCase) -> void +MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver +MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver.GetFormatter() -> MessagePack.Formatters.IMessagePackFormatter? +static readonly MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver.Instance -> MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver! \ No newline at end of file diff --git a/src/MessagePack/net6.0/PublicAPI.Unshipped.txt b/src/MessagePack/net6.0/PublicAPI.Unshipped.txt index e69de29bb..8b7f92b5a 100644 --- a/src/MessagePack/net6.0/PublicAPI.Unshipped.txt +++ b/src/MessagePack/net6.0/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +MessagePack.Formatters.EnumAsStringFormatter.EnumAsStringFormatter(bool ignoreCase) -> void +MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver +MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver.GetFormatter() -> MessagePack.Formatters.IMessagePackFormatter? +static readonly MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver.Instance -> MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver! \ No newline at end of file diff --git a/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt b/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb..8b7f92b5a 100644 --- a/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +MessagePack.Formatters.EnumAsStringFormatter.EnumAsStringFormatter(bool ignoreCase) -> void +MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver +MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver.GetFormatter() -> MessagePack.Formatters.IMessagePackFormatter? +static readonly MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver.Instance -> MessagePack.Resolvers.DynamicEnumAsStringIgnoreCaseResolver! \ No newline at end of file