-Added EnumMemberAttribute support to StringEnumConverter
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using NUnit.Framework;
|
||||
@@ -17,6 +18,11 @@ namespace Newtonsoft.Json.Tests.Converters
|
||||
public StoreColor? NullableStoreColor2 { get; set; }
|
||||
}
|
||||
|
||||
public class EnumContainer<T>
|
||||
{
|
||||
public T Enum { get; set; }
|
||||
}
|
||||
|
||||
public enum NegativeEnum
|
||||
{
|
||||
Negative = -1,
|
||||
@@ -24,12 +30,88 @@ namespace Newtonsoft.Json.Tests.Converters
|
||||
Positive = 1
|
||||
}
|
||||
|
||||
#if !NET20
|
||||
public enum NamedEnum
|
||||
{
|
||||
[EnumMember(Value = "@first")]
|
||||
First,
|
||||
[EnumMember(Value = "@second")]
|
||||
Second,
|
||||
Third
|
||||
}
|
||||
|
||||
public enum NamedEnumDuplicate
|
||||
{
|
||||
[EnumMember(Value = "Third")]
|
||||
First,
|
||||
[EnumMember(Value = "@second")]
|
||||
Second,
|
||||
Third
|
||||
}
|
||||
#endif
|
||||
|
||||
public class NegativeEnumClass
|
||||
{
|
||||
public NegativeEnum Value1 { get; set; }
|
||||
public NegativeEnum Value2 { get; set; }
|
||||
}
|
||||
|
||||
#if !NET20
|
||||
[Test]
|
||||
[ExpectedException(typeof(Exception), ExpectedMessage = "Enum name 'Third' already exists on enum 'NamedEnumDuplicate'.")]
|
||||
public void NamedEnumDuplicateTest()
|
||||
{
|
||||
EnumContainer<NamedEnumDuplicate> c = new EnumContainer<NamedEnumDuplicate>
|
||||
{
|
||||
Enum = NamedEnumDuplicate.First
|
||||
};
|
||||
|
||||
JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SerializeNameEnumTest()
|
||||
{
|
||||
EnumContainer<NamedEnum> c = new EnumContainer<NamedEnum>
|
||||
{
|
||||
Enum = NamedEnum.First
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter());
|
||||
Assert.AreEqual(@"{
|
||||
""Enum"": ""@first""
|
||||
}", json);
|
||||
|
||||
c = new EnumContainer<NamedEnum>
|
||||
{
|
||||
Enum = NamedEnum.Third
|
||||
};
|
||||
|
||||
json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter());
|
||||
Assert.AreEqual(@"{
|
||||
""Enum"": ""Third""
|
||||
}", json);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializeNameEnumTest()
|
||||
{
|
||||
string json = @"{
|
||||
""Enum"": ""@first""
|
||||
}";
|
||||
|
||||
EnumContainer<NamedEnum> c = JsonConvert.DeserializeObject<EnumContainer<NamedEnum>>(json, new StringEnumConverter());
|
||||
Assert.AreEqual(NamedEnum.First, c.Enum);
|
||||
|
||||
json = @"{
|
||||
""Enum"": ""Third""
|
||||
}";
|
||||
|
||||
c = JsonConvert.DeserializeObject<EnumContainer<NamedEnum>>(json, new StringEnumConverter());
|
||||
Assert.AreEqual(NamedEnum.Third, c.Enum);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Test]
|
||||
public void SerializeEnumClass()
|
||||
{
|
||||
|
||||
@@ -24,22 +24,31 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json.Utilities;
|
||||
|
||||
namespace Newtonsoft.Json.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an <see cref="Enum"/> to and from its name string value.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Converts an <see cref="Enum"/> to and from its name string value.
|
||||
/// </summary>
|
||||
public class StringEnumConverter : JsonConverter
|
||||
{
|
||||
private readonly Dictionary<Type, BidirectionalDictionary<string, string>> _enumMemberNamesPerType = new Dictionary<Type, BidirectionalDictionary<string, string>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the written enum text should be camel case.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if the written enum text will be camel case; otherwise, <c>false</c>.</value>
|
||||
public bool CamelCaseText { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes the JSON representation of the object.
|
||||
/// </summary>
|
||||
@@ -54,21 +63,26 @@ namespace Newtonsoft.Json.Converters
|
||||
return;
|
||||
}
|
||||
|
||||
Enum e = (Enum) value;
|
||||
Enum e = (Enum)value;
|
||||
|
||||
string enumName = e.ToString("G");
|
||||
|
||||
if (char.IsNumber(enumName[0]) || enumName[0] == '-')
|
||||
{
|
||||
// enum value didn't have an option against it
|
||||
// fallback to writing number value
|
||||
writer.WriteValue(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CamelCaseText)
|
||||
enumName = StringUtils.ToCamelCase(enumName);
|
||||
BidirectionalDictionary<string, string> map = GetEnumNameMap(e.GetType());
|
||||
|
||||
writer.WriteValue(enumName);
|
||||
string resolvedEnumName;
|
||||
map.TryGetByFirst(enumName, out resolvedEnumName);
|
||||
resolvedEnumName = resolvedEnumName ?? enumName;
|
||||
|
||||
if (CamelCaseText)
|
||||
resolvedEnumName = StringUtils.ToCamelCase(resolvedEnumName);
|
||||
|
||||
writer.WriteValue(resolvedEnumName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +97,8 @@ namespace Newtonsoft.Json.Converters
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
Type t = (ReflectionUtils.IsNullableType(objectType))
|
||||
? Nullable.GetUnderlyingType(objectType)
|
||||
: objectType;
|
||||
? Nullable.GetUnderlyingType(objectType)
|
||||
: objectType;
|
||||
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
@@ -95,26 +109,84 @@ namespace Newtonsoft.Json.Converters
|
||||
}
|
||||
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
return Enum.Parse(t, reader.Value.ToString(), true);
|
||||
|
||||
{
|
||||
var map = GetEnumNameMap(t);
|
||||
string resolvedEnumName;
|
||||
map.TryGetBySecond(reader.Value.ToString(), out resolvedEnumName);
|
||||
resolvedEnumName = resolvedEnumName ?? reader.Value.ToString();
|
||||
|
||||
return Enum.Parse(t, resolvedEnumName, true);
|
||||
}
|
||||
|
||||
if (reader.TokenType == JsonToken.Integer)
|
||||
return ConvertUtils.ConvertOrCast(reader.Value, CultureInfo.InvariantCulture, t);
|
||||
|
||||
|
||||
throw new Exception("Unexpected token when parsing enum. Expected String or Integer, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cached representation of the Enum string representation to respect per Enum field name.
|
||||
/// </summary>
|
||||
/// <param name="t">The type of the Enum.</param>
|
||||
/// <returns>A map of enum field name to either the field name, or the configured enum member name (<see cref="EnumMemberAttribute"/>).</returns>
|
||||
private BidirectionalDictionary<string, string> GetEnumNameMap(Type t)
|
||||
{
|
||||
BidirectionalDictionary<string, string> map;
|
||||
|
||||
if (!_enumMemberNamesPerType.TryGetValue(t, out map))
|
||||
{
|
||||
lock (_enumMemberNamesPerType)
|
||||
{
|
||||
if (_enumMemberNamesPerType.TryGetValue(t, out map))
|
||||
return map;
|
||||
|
||||
map = new BidirectionalDictionary<string, string>(
|
||||
StringComparer.OrdinalIgnoreCase,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (FieldInfo f in t.GetFields())
|
||||
{
|
||||
string n1 = f.Name;
|
||||
string n2;
|
||||
|
||||
#if !NET20
|
||||
n2 = f.GetCustomAttributes(typeof (EnumMemberAttribute), true)
|
||||
.Cast<EnumMemberAttribute>()
|
||||
.Select(a => a.Value)
|
||||
.SingleOrDefault() ?? f.Name;
|
||||
#else
|
||||
n2 = f.Name;
|
||||
#endif
|
||||
|
||||
string s;
|
||||
if (map.TryGetBySecond(n2, out s))
|
||||
{
|
||||
throw new Exception("Enum name '{0}' already exists on enum '{1}'."
|
||||
.FormatWith(CultureInfo.InvariantCulture, n2, t.Name));
|
||||
}
|
||||
|
||||
map.Add(n1, n2);
|
||||
}
|
||||
|
||||
_enumMemberNamesPerType[t] = map;
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance can convert the specified object type.
|
||||
/// </summary>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
||||
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
Type t = (ReflectionUtils.IsNullableType(objectType))
|
||||
? Nullable.GetUnderlyingType(objectType)
|
||||
: objectType;
|
||||
? Nullable.GetUnderlyingType(objectType)
|
||||
: objectType;
|
||||
|
||||
return t.IsEnum;
|
||||
}
|
||||
|
||||
@@ -177,12 +177,15 @@ namespace Newtonsoft.Json.Linq
|
||||
|
||||
return b1.CompareTo(b2);
|
||||
case JTokenType.Date:
|
||||
#if !NET20
|
||||
if (objA is DateTime)
|
||||
{
|
||||
#endif
|
||||
DateTime date1 = Convert.ToDateTime(objA, CultureInfo.InvariantCulture);
|
||||
DateTime date2 = Convert.ToDateTime(objB, CultureInfo.InvariantCulture);
|
||||
|
||||
return date1.CompareTo(date2);
|
||||
#if !NET20
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -194,6 +197,7 @@ namespace Newtonsoft.Json.Linq
|
||||
|
||||
return date1.CompareTo(date2);
|
||||
}
|
||||
#endif
|
||||
case JTokenType.Bytes:
|
||||
if (!(objB is byte[]))
|
||||
throw new ArgumentException("Object must be of type byte[].");
|
||||
|
||||
Reference in New Issue
Block a user