129 lines
5.2 KiB
C#
129 lines
5.2 KiB
C#
|
// 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<T> : IMessagePackFormatter<T>
|
|||
|
{
|
|||
|
private readonly IReadOnlyDictionary<string, T> nameValueMapping;
|
|||
|
private readonly IReadOnlyDictionary<T, string> valueNameMapping;
|
|||
|
private readonly IReadOnlyDictionary<string, string> clrToSerializationName;
|
|||
|
private readonly IReadOnlyDictionary<string, string> serializationToClrName;
|
|||
|
private readonly bool enumMemberOverridesPresent;
|
|||
|
private readonly bool isFlags;
|
|||
|
|
|||
|
public EnumAsStringFormatter()
|
|||
|
{
|
|||
|
this.isFlags = typeof(T).GetCustomAttribute<FlagsAttribute>() is object;
|
|||
|
|
|||
|
var fields = typeof(T).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static);
|
|||
|
var nameValueMapping = new Dictionary<string, T>(fields.Length);
|
|||
|
var valueNameMapping = new Dictionary<T, string>();
|
|||
|
Dictionary<string, string> clrToSerializationName = null;
|
|||
|
Dictionary<string, string> 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<EnumMemberAttribute>();
|
|||
|
if (attribute?.IsValueSetExplicitly ?? false)
|
|||
|
{
|
|||
|
clrToSerializationName = clrToSerializationName ?? new Dictionary<string, string>();
|
|||
|
serializationToClrName = serializationToClrName ?? new Dictionary<string, string>();
|
|||
|
|
|||
|
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<string, string> 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);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|