-Added EnumMemberAttribute support to StringEnumConverter
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -17,6 +18,11 @@ namespace Newtonsoft.Json.Tests.Converters
|
|||||||
public StoreColor? NullableStoreColor2 { get; set; }
|
public StoreColor? NullableStoreColor2 { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class EnumContainer<T>
|
||||||
|
{
|
||||||
|
public T Enum { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public enum NegativeEnum
|
public enum NegativeEnum
|
||||||
{
|
{
|
||||||
Negative = -1,
|
Negative = -1,
|
||||||
@@ -24,12 +30,88 @@ namespace Newtonsoft.Json.Tests.Converters
|
|||||||
Positive = 1
|
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 class NegativeEnumClass
|
||||||
{
|
{
|
||||||
public NegativeEnum Value1 { get; set; }
|
public NegativeEnum Value1 { get; set; }
|
||||||
public NegativeEnum Value2 { 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]
|
[Test]
|
||||||
public void SerializeEnumClass()
|
public void SerializeEnumClass()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,22 +24,31 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using Newtonsoft.Json.Utilities;
|
using Newtonsoft.Json.Utilities;
|
||||||
|
|
||||||
namespace Newtonsoft.Json.Converters
|
namespace Newtonsoft.Json.Converters
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts an <see cref="Enum"/> to and from its name string value.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts an <see cref="Enum"/> to and from its name string value.
|
/// Converts an <see cref="Enum"/> to and from its name string value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StringEnumConverter : JsonConverter
|
public class StringEnumConverter : JsonConverter
|
||||||
{
|
{
|
||||||
|
private readonly Dictionary<Type, BidirectionalDictionary<string, string>> _enumMemberNamesPerType = new Dictionary<Type, BidirectionalDictionary<string, string>>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the written enum text should be camel case.
|
/// Gets or sets a value indicating whether the written enum text should be camel case.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if the written enum text will be camel case; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if the written enum text will be camel case; otherwise, <c>false</c>.</value>
|
||||||
public bool CamelCaseText { get; set; }
|
public bool CamelCaseText { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the JSON representation of the object.
|
/// Writes the JSON representation of the object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -54,21 +63,26 @@ namespace Newtonsoft.Json.Converters
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Enum e = (Enum) value;
|
Enum e = (Enum)value;
|
||||||
|
|
||||||
string enumName = e.ToString("G");
|
string enumName = e.ToString("G");
|
||||||
|
|
||||||
if (char.IsNumber(enumName[0]) || enumName[0] == '-')
|
if (char.IsNumber(enumName[0]) || enumName[0] == '-')
|
||||||
{
|
{
|
||||||
// enum value didn't have an option against it
|
|
||||||
// fallback to writing number value
|
|
||||||
writer.WriteValue(value);
|
writer.WriteValue(value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (CamelCaseText)
|
BidirectionalDictionary<string, string> map = GetEnumNameMap(e.GetType());
|
||||||
enumName = StringUtils.ToCamelCase(enumName);
|
|
||||||
|
|
||||||
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)
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
Type t = (ReflectionUtils.IsNullableType(objectType))
|
Type t = (ReflectionUtils.IsNullableType(objectType))
|
||||||
? Nullable.GetUnderlyingType(objectType)
|
? Nullable.GetUnderlyingType(objectType)
|
||||||
: objectType;
|
: objectType;
|
||||||
|
|
||||||
if (reader.TokenType == JsonToken.Null)
|
if (reader.TokenType == JsonToken.Null)
|
||||||
{
|
{
|
||||||
@@ -95,26 +109,84 @@ namespace Newtonsoft.Json.Converters
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reader.TokenType == JsonToken.String)
|
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)
|
if (reader.TokenType == JsonToken.Integer)
|
||||||
return ConvertUtils.ConvertOrCast(reader.Value, CultureInfo.InvariantCulture, t);
|
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));
|
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>
|
/// <summary>
|
||||||
/// Determines whether this instance can convert the specified object type.
|
/// Determines whether this instance can convert the specified object type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="objectType">Type of the object.</param>
|
/// <param name="objectType">Type of the object.</param>
|
||||||
/// <returns>
|
/// <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>
|
/// </returns>
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
{
|
{
|
||||||
Type t = (ReflectionUtils.IsNullableType(objectType))
|
Type t = (ReflectionUtils.IsNullableType(objectType))
|
||||||
? Nullable.GetUnderlyingType(objectType)
|
? Nullable.GetUnderlyingType(objectType)
|
||||||
: objectType;
|
: objectType;
|
||||||
|
|
||||||
return t.IsEnum;
|
return t.IsEnum;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,12 +177,15 @@ namespace Newtonsoft.Json.Linq
|
|||||||
|
|
||||||
return b1.CompareTo(b2);
|
return b1.CompareTo(b2);
|
||||||
case JTokenType.Date:
|
case JTokenType.Date:
|
||||||
|
#if !NET20
|
||||||
if (objA is DateTime)
|
if (objA is DateTime)
|
||||||
{
|
{
|
||||||
|
#endif
|
||||||
DateTime date1 = Convert.ToDateTime(objA, CultureInfo.InvariantCulture);
|
DateTime date1 = Convert.ToDateTime(objA, CultureInfo.InvariantCulture);
|
||||||
DateTime date2 = Convert.ToDateTime(objB, CultureInfo.InvariantCulture);
|
DateTime date2 = Convert.ToDateTime(objB, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
return date1.CompareTo(date2);
|
return date1.CompareTo(date2);
|
||||||
|
#if !NET20
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -194,6 +197,7 @@ namespace Newtonsoft.Json.Linq
|
|||||||
|
|
||||||
return date1.CompareTo(date2);
|
return date1.CompareTo(date2);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
case JTokenType.Bytes:
|
case JTokenType.Bytes:
|
||||||
if (!(objB is byte[]))
|
if (!(objB is byte[]))
|
||||||
throw new ArgumentException("Object must be of type byte[].");
|
throw new ArgumentException("Object must be of type byte[].");
|
||||||
|
|||||||
Reference in New Issue
Block a user