问题
I have an enum, for example:
[JsonConverter(typeof(MyJSONConverter))]
public enum MyEnum
{
a,
b,
c
}
I have put the JsonConverter
attribute on it.
I have defined the converter class:
public class MyJSONConverter : JsonConverter<MyEnum>
{
public MyJSONConverter()
{
}
public override void WriteJson(JsonWriter writer, MyEnum value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override MyEnum ReadJson(JsonReader reader, Type objectType, MyEnum existingValue, bool hasExistingValue, JsonSerializer serializer)
{
switch (reader.Value)
{
case "a":
return MyEnum.a;
case "b":
return MyEnum.b;
case "c":
return MyEnum.c;
}
return serializer.Deserialize<MyEnum>(reader);
}
public override bool CanRead
{
get
{
return true;
}
}
public override bool CanWrite
{
get
{
return false;
}
}
}
Breakpoints dictate the class is being instantiated. But it never calls any of the member methods or properties.
To my knowledge, that's all it should take to work. It does not work.
I have tried TypeConverter and other classes that inherit JsonConverter, including the one without a generic argument, and the StringEnumConverter. None of them work.
Although seemingly not necessary in any examples I have read, I have tried adding it as a converter to my settings:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Auto;
settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
settings.ContractResolver = new CShouldSerializeContractResolver();
settings.Converters = new JsonConverter[] { new MyJSONConverter() };
JsonSerializer = JsonSerializer.Create(settings);
If I manually add it to my converters, the converter class gets instantiated once, upon instantiating the JsonSerializer itself, and upon attempting to deserialize. It seems to never use any instances that are created.
The error clearly states to attempt to use a TypeConverter, but none of them work.
Could not convert string 'blahblah' to dictionary key type 'MyEnum'.
Create a TypeConverter to convert from the string to the key type object. Path
'Array[0].Keys.blahblah', line 1, position 1388.
Complete fiddle here, with exception occurring: https://dotnetfiddle.net/itCbER
Why doesn't Newtonsoft use any of the documented methods of converting?
回答1:
I've used a TypeSafeEnum with JsonConverter to control how values are transposed between my code and an external vendor API.
First, we setup our abstract TypeSafeEnumBase
class that will be used by multiple objects.
// define a base class for our type safe enum
public abstract class TypeSafeEnumBase
{
// the types for Name and Value could be different if needed...
protected readonly string Name; // would be string value on typical enum
protected readonly int Value; // would be int value on typical enum
protected TypeSafeEnumBase(int value, string name)
{
this.Name = name;
this.Value = value;
}
public override string ToString()
{
return Name;
}
}
Next, we implement our TypeSafeEnum
s that will inherit from the base enum. I've implemented two in this example, A class for MyEnum
from your example and MemberLanguage
, both inherit from the TypeSafeEnumBase
. There is no actual enum
being used, e.g. public enum MyEnum {}
. I've added MemberLanguage
to help show the benefit of the TypeSafeEnumConversion
class that's used in the next step.
// define TypeSafeEnumBase that's a sealed class and inherits from the TypeSafeEnumBase
public sealed class MyEnum : TypeSafeEnumBase
{
// define 'enums'
public static readonly MyEnum a = new MyEnum(1, "a");
public static readonly MyEnum b = new MyEnum(2, "b");
public static readonly MyEnum c = new MyEnum(3, "c");
// add additional 'enums'...
private MyEnum(int value, string name) :
base(value, name)
{
}
public static IEnumerable<MyEnum> MyEnums()
{
// this method only needed if you have to iterate over enums
// and will need to be maintained as new MyEnums are added
yield return a;
yield return b;
yield return c;
}
public static bool TryParse(int value, out MyEnum language)
{
return TryParse(value.ToString(), out language);
}
public static bool TryParse(string value, out MyEnum language)
{
try
{
language = Parse(value);
return true;
}
catch
{
language = null;
return false;
}
}
public static MyEnum Parse(int value)
{
return Parse(value.ToString());
}
public static MyEnum Parse(string value)
{
switch (value)
{
case nameof(a):
case "1":
return MyEnum.a;
case nameof(b):
case "2":
return MyEnum.b;
case nameof(c):
case "3":
return MyEnum.c;
default:
return null;
// throw new ArgumentOutOfRangeException(nameof(value), $"Unable to parse for value, '{value}'. Not found.");
}
}
}
public sealed class MemberLanguage : TypeSafeEnumBase
{
// define 'enums'
public static readonly MemberLanguage English = new MemberLanguage(1, "English");
public static readonly MemberLanguage Spanish = new MemberLanguage(2, "Spanish");
// add additional language types...
// public static readonly MemberLanguage Farsi = new MemberLanguage(3, "Farsi");
// etc...
private MemberLanguage(int value, string name) :
base(value, name)
{
}
public static bool TryParse(int value, out MemberLanguage language)
{
return TryParse(value.ToString(), out language);
}
public static bool TryParse(string value, out MemberLanguage language)
{
try
{
language = Parse(value);
return true;
}
catch
{
language = null;
return false;
}
}
public static MemberLanguage Parse(int value)
{
return Parse(value.ToString());
}
public static MemberLanguage Parse(string value)
{
switch (value)
{
case nameof(English):
case "ENG":
case "1":
return MemberLanguage.English;
case nameof(Spanish):
case "SPN":
case "2":
return MemberLanguage.Spanish;
default:
return null;
// throw new ArgumentOutOfRangeException(nameof(value), $"Unable to parse for value, '{value}'. Not found.");
}
}
}
Next, we create a conversion helper for our TypeSafeEnum
s.
By creating this helper we can ensure our JsonConverter
will not need to change should we add additional type safe enums. Should we add more, this class will get updated but the JsonConverter
will not need to be updated.
public class TypeSafeEnumConversion
{
public static object ConvertToTypeSafeEnum(string name, string value)
{
object returnValue = null;
switch (name)
{
case nameof(MemberLanguage):
returnValue = MemberLanguage.Parse(value);
break;
case nameof(MyEnum):
returnValue = MyEnum.Parse(value);
break;
//case nameof(SomeOtherEnum):
// returnValue = SomeOtherEnum.Parse(value);
// break;
}
return returnValue;
}
}
Now we create our custom TypeSafeEnumJsonConverter
class that implements the JsonConverter
object, and will call our conversion helper object. Again, this allows us to keep this converter object isolated, and prevents the need for changes to it should we add additional type safe enum objects
public class TypeSafeEnumJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var types = new[] { typeof(TypeSafeEnumBase) };
return types.Any(t => t == objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string name = objectType.Name;
string value = serializer.Deserialize(reader).ToString();
// call our custom conversion object
return TypeSafeEnumConversion.ConvertToTypeSafeEnum(name, value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null
&& serializer.NullValueHandling == NullValueHandling.Ignore)
{
return;
}
writer.WriteValue(value.ToString());
}
}
How it's used...
Create an object that uses our TypeSafeEnum
s.
public class SomeThing
{
public string Name { get; set; }
[JsonProperty("my_enum")]
[JsonConverter(typeof(TypeSafeEnumJsonConverter))]
public MyEnum MyEnum { get; set; }
[JsonProperty("language_preference")]
[JsonConverter(typeof(TypeSafeEnumJsonConverter))]
public MemberLanguage LanguagePreference { get; set; }
public override string ToString()
{
return $"{nameof(SomeThing)} : {MyEnum} : {LanguagePreference}";
}
}
Usage...
internal class Program
{
static void Main(string[] args)
{
IList<string> jsonDatas = new List<string>();
// Serialize
foreach (MyEnum e in MyEnum.MyEnums())
{
var thing = new SomeThing();
thing.Name = e.ToString().ToUpper();
thing.LanguagePreference = MemberLanguage.English;
thing.MyEnum = e;
string o = JsonConvert.SerializeObject(thing);
jsonDatas.Add(o);
Console.WriteLine(o);
}
// Deserialize
foreach (string json in jsonDatas)
{
var thing = JsonConvert.DeserializeObject<SomeThing>(json);
Console.WriteLine(thing.ToString());
}
}
}
// Serialized OUTPUT:
// {"Name":"A","my_enum":"a","language_preference":"English"}
// {"Name":"B","my_enum":"b","language_preference":"English"}
// {"Name":"C","my_enum":"c","language_preference":"English"}
// Deserialize OUTPUT:
// SomeThing : a : English
// SomeThing : b : English
// SomeThing : c : English
来源:https://stackoverflow.com/questions/63640608/newtonsoft-json-ignores-typeconverters-and-or-jsonconverters-regardless-of-how