diff options
author | JamesNK <james@newtonking.com> | 2011-03-26 02:49:13 +0300 |
---|---|---|
committer | JamesNK <james@newtonking.com> | 2011-03-26 02:49:13 +0300 |
commit | dffe08021d47ba79c3a0fe9ecd96d7e1b871b27e (patch) | |
tree | bb29b97c9ae2153718927901bbea61473608b451 | |
parent | 39056de2389eb65c2c92f98ce0787fd29d6d1d6c (diff) |
-Added EnumMemberAttribute support to StringEnumConverter
-rw-r--r-- | Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs | 82 | ||||
-rw-r--r-- | Src/Newtonsoft.Json/Converters/StringEnumConverter.cs | 100 | ||||
-rw-r--r-- | Src/Newtonsoft.Json/Linq/JValue.cs | 4 |
3 files changed, 172 insertions, 14 deletions
diff --git a/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs index 1b33332..2d5c8f5 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs @@ -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()
{
diff --git a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs index 83e7b90..1608756 100644 --- a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs +++ b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs @@ -24,7 +24,11 @@ #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
@@ -32,14 +36,19 @@ 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
{
+ BidirectionalDictionary<string, string> map = GetEnumNameMap(e.GetType());
+
+ string resolvedEnumName;
+ map.TryGetByFirst(enumName, out resolvedEnumName);
+ resolvedEnumName = resolvedEnumName ?? enumName;
+
if (CamelCaseText)
- enumName = StringUtils.ToCamelCase(enumName);
+ resolvedEnumName = StringUtils.ToCamelCase(resolvedEnumName);
- writer.WriteValue(enumName);
+ 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;
}
diff --git a/Src/Newtonsoft.Json/Linq/JValue.cs b/Src/Newtonsoft.Json/Linq/JValue.cs index 4b05a58..6bd01a9 100644 --- a/Src/Newtonsoft.Json/Linq/JValue.cs +++ b/Src/Newtonsoft.Json/Linq/JValue.cs @@ -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[].");
|