Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit cf87d2f

Browse files
committed
Treat Enum Flags as a Numeric Type and Encode it in a map key. fixes #328
1 parent 18f6060 commit cf87d2f

File tree

3 files changed

+159
-79
lines changed

3 files changed

+159
-79
lines changed

src/ServiceStack.Text/Json/JsonWriter.Generic.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ public static TypeInfo GetTypeInfo()
164164
static JsonWriter()
165165
{
166166
TypeInfo = new TypeInfo {
167-
EncodeMapKey = typeof(T) == typeof(bool) || typeof(T).IsNumericType()
167+
EncodeMapKey = typeof(T) == typeof(bool) || typeof(T).IsNumericType()
168168
};
169169

170170
CacheFn = typeof(T) == typeof(object)

src/ServiceStack.Text/ReflectionExtensions.cs

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -148,33 +148,82 @@ public static bool AllHaveInterfacesOfType(
148148

149149
public static bool IsNumericType(this Type type)
150150
{
151-
if (!type.IsValueType()) return false;
152-
return type.IsIntegerType() || type.IsRealNumberType();
153-
}
151+
if (type == null) return false;
152+
153+
switch (Type.GetTypeCode(type))
154+
{
155+
case TypeCode.Byte:
156+
case TypeCode.Decimal:
157+
case TypeCode.Double:
158+
case TypeCode.Int16:
159+
case TypeCode.Int32:
160+
case TypeCode.Int64:
161+
case TypeCode.SByte:
162+
case TypeCode.Single:
163+
case TypeCode.UInt16:
164+
case TypeCode.UInt32:
165+
case TypeCode.UInt64:
166+
return true;
154167

168+
case TypeCode.Object:
169+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
170+
{
171+
return IsNumericType(Nullable.GetUnderlyingType(type));
172+
}
173+
if (type.IsEnum)
174+
{
175+
return type.IsEnumFlags();
176+
}
177+
return false;
178+
}
179+
return false;
180+
}
181+
155182
public static bool IsIntegerType(this Type type)
156183
{
157-
if (!type.IsValueType()) return false;
184+
if (type == null) return false;
158185

159-
var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
160-
return underlyingType == typeof(byte)
161-
|| underlyingType == typeof(sbyte)
162-
|| underlyingType == typeof(short)
163-
|| underlyingType == typeof(ushort)
164-
|| underlyingType == typeof(int)
165-
|| underlyingType == typeof(uint)
166-
|| underlyingType == typeof(long)
167-
|| underlyingType == typeof(ulong);
186+
switch (Type.GetTypeCode(type))
187+
{
188+
case TypeCode.Byte:
189+
case TypeCode.Int16:
190+
case TypeCode.Int32:
191+
case TypeCode.Int64:
192+
case TypeCode.SByte:
193+
case TypeCode.UInt16:
194+
case TypeCode.UInt32:
195+
case TypeCode.UInt64:
196+
return true;
197+
198+
case TypeCode.Object:
199+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
200+
{
201+
return IsNumericType(Nullable.GetUnderlyingType(type));
202+
}
203+
return false;
204+
}
205+
return false;
168206
}
169207

170208
public static bool IsRealNumberType(this Type type)
171209
{
172-
if (!type.IsValueType()) return false;
210+
if (type == null) return false;
211+
212+
switch (Type.GetTypeCode(type))
213+
{
214+
case TypeCode.Decimal:
215+
case TypeCode.Double:
216+
case TypeCode.Single:
217+
return true;
173218

174-
var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
175-
return underlyingType == typeof(float)
176-
|| underlyingType == typeof(double)
177-
|| underlyingType == typeof(decimal);
219+
case TypeCode.Object:
220+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
221+
{
222+
return IsNumericType(Nullable.GetUnderlyingType(type));
223+
}
224+
return false;
225+
}
226+
return false;
178227
}
179228

180229
public static Type GetTypeWithGenericInterfaceOf(this Type type, Type genericInterfaceType)
@@ -1035,6 +1084,15 @@ public static bool IsEnum(this Type type)
10351084
#endif
10361085
}
10371086

1087+
public static bool IsEnumFlags(this Type type)
1088+
{
1089+
#if NETFX_CORE
1090+
return type.GetTypeInfo().IsEnum && type.FirstAttribute<FlagsAttribute>(false) != null;
1091+
#else
1092+
return type.IsEnum && type.FirstAttribute<FlagsAttribute>(false) != null;
1093+
#endif
1094+
}
1095+
10381096
public static bool IsUnderlyingEnum(this Type type)
10391097
{
10401098
#if NETFX_CORE
Lines changed: 82 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,55 @@
11
using System;
2+
using System.Collections.Generic;
23
using NUnit.Framework;
34

45
namespace ServiceStack.Text.Tests
56
{
6-
[TestFixture]
7-
public class EnumTests
8-
{
9-
[SetUp]
10-
public void SetUp()
11-
{
12-
JsConfig.Reset();
13-
}
14-
15-
public enum EnumWithoutFlags
16-
{
17-
One = 1,
18-
Two = 2
19-
}
20-
21-
[Flags]
22-
public enum EnumWithFlags
23-
{
24-
One = 1,
25-
Two = 2
26-
}
27-
28-
public class ClassWithEnums
29-
{
30-
public EnumWithFlags FlagsEnum { get; set; }
31-
public EnumWithoutFlags NoFlagsEnum { get; set; }
32-
public EnumWithFlags? NullableFlagsEnum { get; set; }
33-
public EnumWithoutFlags? NullableNoFlagsEnum { get; set; }
34-
}
35-
36-
[Test]
37-
public void Can_correctly_serialize_enums()
38-
{
39-
var item = new ClassWithEnums
40-
{
41-
FlagsEnum = EnumWithFlags.One,
42-
NoFlagsEnum = EnumWithoutFlags.One,
43-
NullableFlagsEnum = EnumWithFlags.Two,
44-
NullableNoFlagsEnum = EnumWithoutFlags.Two
45-
};
46-
47-
const string expected = "{\"FlagsEnum\":1,\"NoFlagsEnum\":\"One\",\"NullableFlagsEnum\":2,\"NullableNoFlagsEnum\":\"Two\"}";
48-
var text = JsonSerializer.SerializeToString(item);
49-
50-
Assert.AreEqual(expected, text);
51-
}
7+
[TestFixture]
8+
public class EnumTests
9+
{
10+
[SetUp]
11+
public void SetUp()
12+
{
13+
JsConfig.Reset();
14+
}
15+
16+
public enum EnumWithoutFlags
17+
{
18+
One = 1,
19+
Two = 2
20+
}
21+
22+
[Flags]
23+
public enum EnumWithFlags
24+
{
25+
One = 1,
26+
Two = 2
27+
}
28+
29+
public class ClassWithEnums
30+
{
31+
public EnumWithFlags FlagsEnum { get; set; }
32+
public EnumWithoutFlags NoFlagsEnum { get; set; }
33+
public EnumWithFlags? NullableFlagsEnum { get; set; }
34+
public EnumWithoutFlags? NullableNoFlagsEnum { get; set; }
35+
}
36+
37+
[Test]
38+
public void Can_correctly_serialize_enums()
39+
{
40+
var item = new ClassWithEnums
41+
{
42+
FlagsEnum = EnumWithFlags.One,
43+
NoFlagsEnum = EnumWithoutFlags.One,
44+
NullableFlagsEnum = EnumWithFlags.Two,
45+
NullableNoFlagsEnum = EnumWithoutFlags.Two
46+
};
47+
48+
const string expected = "{\"FlagsEnum\":1,\"NoFlagsEnum\":\"One\",\"NullableFlagsEnum\":2,\"NullableNoFlagsEnum\":\"Two\"}";
49+
var text = JsonSerializer.SerializeToString(item);
50+
51+
Assert.AreEqual(expected, text);
52+
}
5253

5354
public void Should_deserialize_enum()
5455
{
@@ -80,19 +81,40 @@ public void CanSerializeSbyteFlag()
8081
Assert.AreEqual("0", val);
8182
}
8283

83-
[Flags]
84-
public enum FlagEnum
85-
{
86-
A,
87-
B
88-
}
89-
90-
[Flags]
91-
public enum SbyteFlagEnum: sbyte
92-
{
93-
A,
94-
B
95-
}
96-
}
84+
[Flags]
85+
public enum FlagEnum
86+
{
87+
A,
88+
B
89+
}
90+
91+
[Flags]
92+
public enum SbyteFlagEnum : sbyte
93+
{
94+
A,
95+
B
96+
}
97+
98+
[Flags]
99+
public enum AnEnum
100+
{
101+
This,
102+
Is,
103+
An,
104+
Enum
105+
}
106+
107+
[Test]
108+
public void Can_use_enum_as_key_in_map()
109+
{
110+
var dto = new Dictionary<AnEnum, int> { { AnEnum.This, 1 } };
111+
var json = dto.ToJson();
112+
json.Print();
113+
114+
var map = json.FromJson<Dictionary<AnEnum, int>>();
115+
Assert.That(map[AnEnum.This], Is.EqualTo(1));
116+
}
117+
118+
}
97119
}
98120

0 commit comments

Comments
 (0)