// 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.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; namespace MessagePack.Formatters { // Note:This implementation is 'not' fastest, should more improve. public sealed class EnumAsStringFormatter : IMessagePackFormatter { private readonly IReadOnlyDictionary nameValueMapping; private readonly IReadOnlyDictionary valueNameMapping; private readonly IReadOnlyDictionary clrToSerializationName; private readonly IReadOnlyDictionary serializationToClrName; private readonly bool enumMemberOverridesPresent; private readonly bool isFlags; 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 valueNameMapping = new Dictionary(); Dictionary clrToSerializationName = null; Dictionary serializationToClrName = null; foreach (FieldInfo enumValueMember in fields) { string name = enumValueMember.Name; T value = (T)enumValueMember.GetValue(null); // Consider the case where the serialized form of the enum value is overridden via an attribute. var attribute = enumValueMember.GetCustomAttribute(); if (attribute?.IsValueSetExplicitly ?? false) { clrToSerializationName = clrToSerializationName ?? new Dictionary(); serializationToClrName = serializationToClrName ?? new Dictionary(); clrToSerializationName.Add(name, attribute.Value); serializationToClrName.Add(attribute.Value, name); name = attribute.Value; this.enumMemberOverridesPresent = true; } nameValueMapping[name] = value; valueNameMapping[value] = name; } this.nameValueMapping = nameValueMapping; this.valueNameMapping = valueNameMapping; this.clrToSerializationName = clrToSerializationName; this.serializationToClrName = serializationToClrName; } public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options) { // Enum.ToString() is slow, so avoid it when we can. if (!this.valueNameMapping.TryGetValue(value, out string valueString)) { // fallback for flags, values with no name, etc valueString = this.GetSerializedNames(value.ToString()); } writer.Write(valueString); } public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { string name = reader.ReadString(); // 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)); } return value; } private string GetClrNames(string serializedNames) { if (this.enumMemberOverridesPresent && this.isFlags && serializedNames.IndexOf(", ", StringComparison.Ordinal) >= 0) { return Translate(serializedNames, this.serializationToClrName); } // We don't need to consider the trivial case of no commas because our caller would have found that in the lookup table and not called us. return serializedNames; } private string GetSerializedNames(string clrNames) { if (this.enumMemberOverridesPresent && this.isFlags && clrNames.IndexOf(", ", StringComparison.Ordinal) >= 0) { return Translate(clrNames, this.clrToSerializationName); } // We don't need to consider the trivial case of no commas because our caller would have found that in the lookup table and not called us. return clrNames; } private static string Translate(string items, IReadOnlyDictionary mapping) { string[] elements = items.Split(','); for (int i = 0; i < elements.Length; i++) { // Trim the leading space if there is one (due to the delimiter being ", "). if (i > 0 && elements[i].Length > 0 && elements[i][0] == ' ') { elements[i] = elements[i].Substring(1); } if (mapping.TryGetValue(elements[i], out string substituteValue)) { elements[i] = substituteValue; } } return string.Join(", ", elements); } } }