How could i deserialize json into a List of enum in C#?
I wrote the following code:
//json "types" : [ "hotel", "spa" ]
public enum eType
{
[Description("hotel")]
kHotel,
[Description("spa")]
kSpa
}
public class HType
{
List<eType> m_types;
[JsonProperty("types")]
public List<eType> HTypes {
get
{
return m_types;
}
set
{
// i did this to try and decide in the setter
// what enum value should be for each type
// making use of the Description attribute
// but throws an exception
}
}
}
//other class
var hTypes = JsonConvert.DeserializeObject<HType>(json);
A custom converter may help.
var hType = JsonConvert.DeserializeObject<HType>(
#"{""types"" : [ ""hotel"", ""spa"" ]}",
new MyEnumConverter());
public class HType
{
public List<eType> types { set; get; }
}
public enum eType
{
[Description("hotel")]
kHotel,
[Description("spa")]
kSpa
}
public class MyEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(eType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var eTypeVal = typeof(eType).GetMembers()
.Where(x => x.GetCustomAttributes(typeof(DescriptionAttribute)).Any())
.FirstOrDefault(x => ((DescriptionAttribute)x.GetCustomAttribute(typeof(DescriptionAttribute))).Description == (string)reader.Value);
if (eTypeVal == null) return Enum.Parse(typeof(eType), (string)reader.Value);
return Enum.Parse(typeof(eType), eTypeVal.Name);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is my version of an enum converter for ANY enum type... it will handle either a numeric value or a string value for the incoming value. As well as nullable vs non-nullable results.
public class MyEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (!objectType.IsEnum)
{
var underlyingType = Nullable.GetUnderlyingType(objectType);
if (underlyingType != null && underlyingType.IsEnum)
objectType = underlyingType;
}
return objectType.IsEnum;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (!objectType.IsEnum)
{
var underlyingType = Nullable.GetUnderlyingType(objectType);
if (underlyingType != null && underlyingType.IsEnum)
objectType = underlyingType;
}
var value = reader.Value;
string strValue;
if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
{
if (existingValue == null || Nullable.GetUnderlyingType(existingValue.GetType()) != null)
return null;
strValue = "0";
}
else
strValue = value.ToString();
int intValue;
if (int.TryParse(strValue, out intValue))
return Enum.ToObject(objectType, intValue);
return Enum.Parse(objectType, strValue);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Related
Here is my dto of action for json input :
public class GreetRequest
{
public string Message{get;set;}
}
When the json is post from the client:
{
"Message":true
}
the json.net would convert the Boolean to string automatically:
true -> “true”
By far,I wrote a converter to enforce the rule:
public class StrictStringConverter : JsonConverter
{
readonly JsonSerializer defaultSerializer = new JsonSerializer();
public override bool CanConvert(Type objectType)
{
return objectType.IsStringType();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.String:
case JsonToken.Null:
return defaultSerializer.Deserialize(reader, objectType);
default:
throw new JsonSerializationException();
}
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static bool IsStringType(this Type type)
{
type = Nullable.GetUnderlyingType(type) ?? type;
if (type == typeof(string))
return true;
return false;
}
}
the converter is configure at startup.cs
services.Configure<MvcNewtonsoftJsonOptions>(options => {
options.SerializerSettings.Converters.Add(new StrictStringConverter());
});
Question:
Is there any out of box way of strict conversion for json input, without writing any converter manually.
I have a flag type of enum and I would like it to be serialized not as string but as number.
There is no problem about serializing but json.net unable to deserialize it.
public class ForceNumericFlagEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (!(Nullable.GetUnderlyingType(objectType) ?? objectType).IsEnum)
return false;
return HasFlagsAttribute(objectType);
}
public override bool CanRead { get { return false; } }
public override bool CanWrite { get { return false; } }
static bool HasFlagsAttribute(Type objectType)
{
return Attribute.IsDefined(Nullable.GetUnderlyingType(objectType) ?? objectType, typeof(System.FlagsAttribute));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class Program
{
public static void Main()
{
try
{
Test(UserCan.ChangeConf | UserCan.ForceOther);
Test(UserCan.DoNothing);
Test(UserCan.ForceOther);
Test(UserCan.DoEverything);
}
catch (Exception ex)
{
Console.WriteLine("Uncaught exception:");
Console.WriteLine(ex);
throw;
}
}
static void Test(UserCan ability)
{
JsonConvert.DefaultSettings = () => GetGlobalJsonSettings();
var settings = GetNEWJsonSettings();
string jsonAbility = JsonConvert.SerializeObject(ability, settings);
Console.WriteLine("\nJSON serialization for {0} \"{1}\":", ability.GetType(), ability);
Console.WriteLine(jsonAbility);
ulong val;
Assert.IsTrue(ulong.TryParse(jsonAbility, out val));
var result1 = JsonConvert.DeserializeObject<UserCan?>(jsonAbility, settings);
var result2 = JsonConvert.DeserializeObject<UserCan>(jsonAbility, settings);
Assert.IsTrue(result1 == ability);
Assert.IsTrue(result2 == ability);
Assert.AreEqual("\"someValue\"", JsonConvert.SerializeObject(NonFlagEnum.SomeValue, settings));
}
public enum NonFlagEnum
{
SomeValue,
}
[Flags]
public enum UserCan : ulong
{
DoNothing = 0,
ChangeConf = 1 << 0,
MonitorOthers = 1 << 1,
ForceOther = 1 << 2,
EditRemoveOthers = 1 << 3,
DoEverything = unchecked((ulong)~0),
}
public static JsonSerializerSettings GetGlobalJsonSettings()
{
JsonSerializerSettings settings = new JsonSerializerSettings
{
Converters = { new StringEnumConverter{ CamelCaseText = true } },
};
return settings;
}
public static JsonSerializerSettings GetNEWJsonSettings()
{
JsonSerializerSettings settings = new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
NullValueHandling = NullValueHandling.Ignore,
ObjectCreationHandling = ObjectCreationHandling.Replace,
TypeNameHandling = TypeNameHandling.Auto,
Converters = { new ForceNumericFlagEnumConverter() },
};
return settings;
}
}
You can see full code to create error here
https://dotnetfiddle.net/y0GnNf
[Newtonsoft.Json.JsonSerializationException: Error converting value 18446744073709551615 to type 'Program+UserCan'. Path '', line 1, position 20.]
You have encountered a known bug in Json.NET with ulong-type enumerations: Failure to deserialize ulong enum #2301
Serialization of ulong enum values works fine,
but deserialization fails on large values.
As a workaround, if you are not using Json.NET's StringEnumConverter, you can add the following converter that correctly handles very large values for ulong enums:
public class FixedUlongEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var enumType = Nullable.GetUnderlyingType(objectType) ?? objectType;
if (!enumType.IsEnum)
return false;
return Enum.GetUnderlyingType(enumType) == typeof(ulong);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var nullableUnderlying = Nullable.GetUnderlyingType(objectType);
var enumType = nullableUnderlying ?? objectType;
var isNullable = nullableUnderlying != null;
switch (reader.MoveToContentAndAssert().TokenType)
{
case JsonToken.Null:
if (!isNullable)
throw new JsonSerializationException(string.Format("Null value for {0}", objectType));
return null;
case JsonToken.Integer:
if (reader.ValueType == typeof(System.Numerics.BigInteger))
{
var bigInteger = (System.Numerics.BigInteger)reader.Value;
if (bigInteger >= ulong.MinValue && bigInteger <= ulong.MaxValue)
{
return Enum.ToObject(enumType, checked((ulong)bigInteger));
}
else
{
throw new JsonSerializationException(string.Format("Value {0} is too large for enum {1}", bigInteger, enumType));
}
}
else
{
return Enum.ToObject(enumType, reader.Value);
}
default:
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
}
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
Demo fiddle #1 here.
And if you are using Json.NET's StringEnumConverter but might sometimes get numeric values for enums anyway, replace it with this fixed version:
public class FixedUlongStringEnumConverter : StringEnumConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Integer && reader.ValueType == typeof(System.Numerics.BigInteger))
{
// Todo: throw an exception if !this.AllowIntegerValues
// https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_Converters_StringEnumConverter_AllowIntegerValues.htm
var enumType = Nullable.GetUnderlyingType(objectType) ?? objectType;
if (Enum.GetUnderlyingType(enumType) == typeof(ulong))
{
var bigInteger = (System.Numerics.BigInteger)reader.Value;
if (bigInteger >= ulong.MinValue && bigInteger <= ulong.MaxValue)
{
return Enum.ToObject(enumType, checked((ulong)bigInteger));
}
}
}
return base.ReadJson(reader, objectType, existingValue, serializer);
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
Now, you have an additional requirement to serialize [Flags] enums, and only [Flags] enums, as integers. You can do this by further subclassing the above converter as follows:
public class ForceNumericFlagEnumConverter : FixedUlongStringEnumConverter
{
static bool HasFlagsAttribute(Type objectType)
{
return Attribute.IsDefined(Nullable.GetUnderlyingType(objectType) ?? objectType, typeof(System.FlagsAttribute));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var enumType = value.GetType();
if (HasFlagsAttribute(enumType))
{
var underlyingType = Enum.GetUnderlyingType(enumType);
var underlyingValue = Convert.ChangeType(value, underlyingType);
writer.WriteValue(underlyingValue);
}
else
{
base.WriteJson(writer, value, serializer);
}
}
}
The workaround requires support for BigInteger which was introduced in .NET Framework 4.0.
Demo fiddle #2 here.
Change ulong to uint :
public enum UserCan : uint
{
DoNothing = 0,
ChangeConf = 1 << 0,
MonitorOthers = 1 << 1,
ForceOther = 1 << 2,
EditRemoveOthers = 1 << 3,
DoEverything = unchecked((uint)~0),
}
Working fiddle:
https://dotnetfiddle.net/DyQ2R7
Assuming a json string like the following:
string json = '{"string_property":"foo_bar", ... other objects here ...}';
I was wondering if there's a way to run a transformation on the parsed object so that instead of getting foo_bar, I'll get foo bar after running the following method (can be anything really)
public string Transform(string s) {
return s.Replace("_"," ");
}
I can manually alter my poco after deserializing, but wondered what would be a "cleaner" approach?
You can transform your string properties as you deserialize your root object by using a custom JsonConverter targeted at all string type values:
public class ReplacingStringConverter : JsonConverter
{
readonly string oldValue;
readonly string newValue;
public ReplacingStringConverter(string oldValue, string newValue)
{
if (string.IsNullOrEmpty(oldValue))
throw new ArgumentException("string.IsNullOrEmpty(oldValue)");
if (newValue == null)
throw new ArgumentNullException("newValue");
this.oldValue = oldValue;
this.newValue = newValue;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JToken.Load(reader);
return s.Replace(oldValue, newValue);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then use it like:
var settings = new JsonSerializerSettings { Converters = new[] { new ReplacingStringConverter("_", "") } };
var result = JsonConvert.DeserializeObject<RootObject>(json, settings);
Note however that if individual string-type properties have their own converters applied directly with [JsonConverter(Type)], those converters will be used in preference to the ReplacingStringConverter in the Converters list.
I've ended up doing the following:
First, create a converter that only reads and all it does is url decode the string.
public class UrlDecoderConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JToken.Load(reader);
return HttpUtility.UrlDecode(s);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, simply add the following to the POCO properties that need to be decoded:
[JsonConverter(typeof(UrlDecoderConverter))]
public string url { get; set; }
How could i deserialize json into a List of enum in C#?
I wrote the following code:
//json "types" : [ "hotel", "spa" ]
public enum eType
{
[Description("hotel")]
kHotel,
[Description("spa")]
kSpa
}
public class HType
{
List<eType> m_types;
[JsonProperty("types")]
public List<eType> HTypes {
get
{
return m_types;
}
set
{
// i did this to try and decide in the setter
// what enum value should be for each type
// making use of the Description attribute
// but throws an exception
}
}
}
//other class
var hTypes = JsonConvert.DeserializeObject<HType>(json);
A custom converter may help.
var hType = JsonConvert.DeserializeObject<HType>(
#"{""types"" : [ ""hotel"", ""spa"" ]}",
new MyEnumConverter());
public class HType
{
public List<eType> types { set; get; }
}
public enum eType
{
[Description("hotel")]
kHotel,
[Description("spa")]
kSpa
}
public class MyEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(eType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var eTypeVal = typeof(eType).GetMembers()
.Where(x => x.GetCustomAttributes(typeof(DescriptionAttribute)).Any())
.FirstOrDefault(x => ((DescriptionAttribute)x.GetCustomAttribute(typeof(DescriptionAttribute))).Description == (string)reader.Value);
if (eTypeVal == null) return Enum.Parse(typeof(eType), (string)reader.Value);
return Enum.Parse(typeof(eType), eTypeVal.Name);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is my version of an enum converter for ANY enum type... it will handle either a numeric value or a string value for the incoming value. As well as nullable vs non-nullable results.
public class MyEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (!objectType.IsEnum)
{
var underlyingType = Nullable.GetUnderlyingType(objectType);
if (underlyingType != null && underlyingType.IsEnum)
objectType = underlyingType;
}
return objectType.IsEnum;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (!objectType.IsEnum)
{
var underlyingType = Nullable.GetUnderlyingType(objectType);
if (underlyingType != null && underlyingType.IsEnum)
objectType = underlyingType;
}
var value = reader.Value;
string strValue;
if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
{
if (existingValue == null || Nullable.GetUnderlyingType(existingValue.GetType()) != null)
return null;
strValue = "0";
}
else
strValue = value.ToString();
int intValue;
if (int.TryParse(strValue, out intValue))
return Enum.ToObject(objectType, intValue);
return Enum.Parse(objectType, strValue);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This question already has answers here:
How to handle both a single item and an array for the same property using JSON.net
(9 answers)
Closed 8 years ago.
I have to read a JSON document, which has a field that can contain different types.
For example can be either a long or an array of integers. I know I will need to use a custom deserializer, but am not sure how.
In the example below the xx field sometimes is a long, otherwise an array of ints.
Any help on how to deal with this is appreciated.
static void JsonTest() {
const string json = #"
{
'Code': 'XYZ',
'Response': {
'Type' : 'S',
'Docs': [
{
'id' : 'test1',
'xx' : 1
},
{
'id' : 'test2',
'xx' : [1, 2, 4, 8]
},
]
}
}";
A a;
try {
a = JsonConvert.DeserializeObject<A>(json);
}
catch( Exception ex ) {
Console.Error.WriteLine(ex.Message);
}
}
public class A {
public string Code;
public TResponse Response;
}
public class TResponse {
public string Type;
public List<Doc> Docs;
}
public class Doc {
public string id;
public int[] xx;
}
My implementation based on the suggestion below (changed array to long from int):
[JsonConverter(typeof(DocConverter))]
public class Doc {
public string id;
public long[] xx;
}
public class DocConverter : JsonConverter {
public override bool CanWrite { get { return false; } }
public override bool CanConvert( Type objectType ) {
return typeof(Doc).IsAssignableFrom(objectType);
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) {
JObject item = JObject.Load(reader);
Doc doc = new Doc();
doc.id = item["id"].ToObject<string>();
if( item["xx"].Type == JTokenType.Long )
doc.xx = new [] { item["xx"].ToObject<long>() };
else
doc.xx = item["xx"].ToObject<long[]>();
return doc;
}
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ) {
throw new NotImplementedException();
}
}
Since xx can either be a long or an array of ints, it makes sense to turn Doc into a class hierarchy. (If it were a single long or an array of longs, it would make sense to read them all into a single class.)
You can do this by using a JsonConverter, like so:
[JsonConverter(typeof(DocConverter))]
public abstract class Doc
{
public string id;
}
[JsonConverter(typeof(NoConverter))] // Prevents infinite recursion when converting a class instance known to be of type DocSingle
public class DocSingle : Doc
{
public long xx;
}
[JsonConverter(typeof(NoConverter))] // Prevents infinite recursion when converting a class instance known to be of type DocList
public class DocList : Doc
{
public int[] xx;
}
public class DocConverter : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override bool CanConvert(Type objectType)
{
return typeof(Doc).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
if (item["xx"].Type == JTokenType.Integer)
{
return item.ToObject<DocSingle>();
}
else
{
return item.ToObject<DocList>();
}
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class NoConverter : JsonConverter
{
public override bool CanRead { get { return false; } }
public override bool CanWrite { get { return false; } }
public override bool CanConvert(Type objectType)
{
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Update
incidentally, if you're willing to simplify your data model to say that xx can either be a single long or an array of longs, you can simplify the code as follows:
[JsonConverter(typeof(DocConverter))]
public sealed class Doc
{
public string id;
public long[] xx;
}
public class DocConverter : JsonConverter
{
public override bool CanWrite { get { return true; } }
public override bool CanConvert(Type objectType)
{
return typeof(Doc).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
var doc = new Doc();
JToken id = item["id"];
if (id != null)
doc.id = id.ToString();
JToken xx = item["xx"];
if (xx != null)
{
if (xx.Type == JTokenType.Integer)
{
var val = (long)xx;
doc.xx = new long[] { val };
}
else if (xx.Type == JTokenType.Array)
{
var val = xx.ToObject<long[]>();
doc.xx = val;
}
else
{
Debug.WriteLine("Unknown type of JToken for \"xx\": " + xx.ToString());
}
}
return doc;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var doc = (Doc)value;
writer.WriteStartObject();
writer.WritePropertyName("id");
writer.WriteValue(doc.id);
var xx = doc.xx;
if (xx != null)
{
writer.WritePropertyName("xx");
if (xx.Length == 1)
{
writer.WriteValue(xx[0]);
}
else
{
writer.WriteStartArray();
foreach (var x in xx)
{
writer.WriteValue(x);
}
writer.WriteEndArray();
}
}
writer.WriteEndObject();
}
}
You have a string, try json.Contains("'Type':'S'").
Then deserialize it to the proper model.