Using the enum:
namespace AppGlobals
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum BoardSymbols
{
[EnumMember(Value = "X")]
First = 'X',
[EnumMember(Value = "O")]
Second = 'O',
[EnumMember(Value = "?")]
EMPTY = '?'
}
}
I would like to define a model for my api:
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Assignment_1
{
public class MyRequest
{
//...
[Required]
[MinLength(9)]
[MaxLength(9)]
[JsonProperty("changeTypes", ItemConverterType = typeof(JsonStringEnumConverter))]
public AppGlobals.BoardSymbols[] GameBoard { get; set; }
}
}
Where GameBoard
should serialize to JSON as an array of strings with names specified by the EnumMember
attributes. This approach is adapted from Deserialize json character as enumeration. However, it does not work. This does works if I change the enum to:
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum BoardSymbols
{
X='X',
Y='Y'
}
But I obviously hit a limit on the 'empty' enumeration. How can I do this?
I did not have AddNewtonsoftJson()
in my startup, converting over fully to Newtonsoft. Now my error is perhaps more actionable:
System.InvalidCastException: Unable to cast object of type 'CustomJsonStringEnumConverter' to type 'Newtonsoft.Json.JsonConverter'.
at Newtonsoft.Json.Serialization.JsonTypeReflector.CreateJsonConverterInstance(Type converterType, Object[] args)
This makes sense, the solution prescribed to me here specified a JsonConverterFactory .. I just need the raw JsonConverter for my use case instead.
TL/DR: You have two basic problems here:
.NET Core 3.0+ has a new built-in JSON serializer System.Text.Json
, and you are mixing up attributes and classes between this new serializer and Json.NET. This is very easy to do when both are installed because they share some class names, such as JsonSerializer
and JsonConverter
.
The new serializer is used by default but, prior to .NET 9, does not support serialization of enums as strings with custom value names; see System.Text.Json: How do I specify a custom name for an enum value? for details.
The easiest way to solve your problem is to switch back to Json.NET as shown here and use attributes, converters and namespaces exclusively from this serializer.
First let's break down the differences and similarities between the two serializers:
System.Text.Json
:
Built into .NET Core 3.0+ automatically and used for JSON serialization by ASP.NET Core 3.0+ by default.
Namespaces System.Text.Json
and System.Text.Json.Serialization
.
Classes including System.Text.Json.Serialization.JsonConverter
, System.Text.Json.Serialization.JsonConverter<T>
and System.Text.Json.JsonSerializer
.
Attributes including System.Text.Json.Serialization.JsonPropertyNameAttribute
, System.Text.Json.Serialization.JsonConverterAttribute
and System.Text.Json.Serialization.JsonExtensionDataAttribute
.
Serialization of enums as strings is supported by System.Text.Json.Serialization.JsonStringEnumConverter
, however renaming via attributes is not implemented.
See this answer to System.Text.Json: How do I specify a custom name for an enum value? for potential workarounds.
Update: as of .NET 9 customization of enum names is finally supported via JsonStringEnumMemberNameAttribute
.
Json.NET:
A 3rd-party library that can be used for serialization in ASP.NET Core 3.0+ by adding a NuGet reference to Microsoft.AspNetCore.Mvc.NewtonsoftJson
and then calling AddNewtonsoftJson()
in Startup.ConfigureServices
.
For details see this answer to Where did IMvcBuilder AddJsonOptions go in .Net Core 3.0? by poke.
Namespaces including Newtonsoft.Json
, Newtonsoft.Json.Converters
, Newtonsoft.Json.Linq
and Newtonsoft.Json.Serialization
among others.
Classes including Newtonsoft.Json.JsonConverter
, Newtonsoft.Json.JsonConverter<T>
and Newtonsoft.Json.JsonSerializer
Attributes including Newtonsoft.Json.JsonPropertyAttribute
, Newtonsoft.Json.JsonConverterAttribute
and Newtonsoft.Json.JsonExtensionDataAttribute
among others.
Serialization of enums as renamed strings is supported automatically by Newtonsoft.Json.Converters.StringEnumConverter
when the EnumMemberAttribute
attribute is applied.
With this in mind, which serializer are you using in your code? Since you helpfully included the namespaces in your question, we can check:
using System.Text.Json.Serialization; // System.Text.Json
using Newtonsoft.Json; // Json.NET
namespace Assignment_1
{
public class MyRequest
{
//...
[JsonProperty( // JsonProperty from Newtonsoft
"changeTypes",
ItemConverterType = typeof(JsonStringEnumConverter)// JsonStringEnumConverter from System.Text.Json
)]
public AppGlobals.BoardSymbols[] GameBoard { get; set; }
}
}
So as you can see, you are mixing up attributes from Newtonsoft with converters from System.Text.Json
, which isn't going to work. (Perhaps you selected the namespaces from a "Resolve -> using ..." right-click in Visual Studio?)
So, how to resolve the problem? Since Json.NET supports renaming of enum values out of the box, the easiest way to resolve your problem is to use this serializer. While possibly not as performant as System.Text.Json
it is much more complete and full-featured.
To do this, remove the namespaces System.Text.Json.Serialization
and System.Text.Json
and references to the type JsonStringEnumConverter
from your code, and modify MyRequest
and BoardSymbols
as follows:
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
namespace Assignment_1
{
public class MyRequest
{
//...
[Required]
[MinLength(9)]
[MaxLength(9)]
[JsonProperty("changeTypes")] // No need to add StringEnumConverter here since it's already applied to the enum itself
public AppGlobals.BoardSymbols[] GameBoard { get; set; }
}
}
namespace AppGlobals
{
[JsonConverter(typeof(StringEnumConverter))]
public enum BoardSymbols
{
[EnumMember(Value = "X")]
First = 'X',
[EnumMember(Value = "O")]
Second = 'O',
[EnumMember(Value = "?")]
EMPTY = '?'
}
}
Then NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson
and in Startup.ConfigureServices
call AddNewtonsoftJson()
:
services.AddMvc()
.AddNewtonsoftJson();
Or if you prefer to use StringEnumConverter
globally:
services.AddMvc()
.AddNewtonsoftJson(o => o.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()));
Do take note of the following comment from the docs
Note: If the
AddNewtonsoftJson
method isn't available, make sure that you installed the Microsoft.AspNetCore.Mvc.NewtonsoftJson package. A common error is to install the Newtonsoft.Json package instead of the Microsoft.AspNetCore.Mvc.NewtonsoftJson package.
Mockup fiddle here.