I am trying to serialize/deserialize a Dictionary<string, object> in C#.
Object can be anything that is serializable.
Json.NET almost works, but if a value within the dictionary is an enum, the deserialization is not correct, as it gets deserialized as a long. TypeNameHandling.All does not make any difference.
Is there any other fast solution to serialization library. The result does not have to be JSON, but must be text.
I also have no influence on the data that is passed to the dictionary. I just have to serialize and deserialize anything that comes into my way.
EDIT: StringEnumConverter does not help. The data gets converted back to Dictionary<string, object>, so the deserializer does not know that the serialized value is an enum. It treats it like an object, with StringEnumConverter it remains a string when deserialized; it gets deserialized as a long without the converter. JSON.NET does not preserve the enum.
The solution i want to provide is an implementation of an existing interface that gets injected into an existing solution that i cannot change.
EDIT2: Here is an example of what i am trying to do
public enum Foo { A, B, C }
public enum Bar { A, B, C }
public class Misc { public Foo Foo { get; set; } }
var dict = new Dictionary<string, object>();
dict.Add("a", Foo.A);
dict.Add("b", Bar.B);
dict.Add("c", new Misc());
// serialize dict to a string s
// deserialize s to a Dictionary<string, object> dict2
Assert.AreEqual(Foo.A, dict2["a"]);
Assert.AreEqual(Bar.B, dict2["b"]);
Important: i cannot control dict; it is actually a custom type that is derived from Dictionary<string, object>: I just have to make sure that all keys and values deserialized are from the same type when deserialized, so that no casting is needed. And again, i do not have to use JSON; maybe there is some other serializer that can handle the job!?
Presumably you are already serializing your dictionary with TypeNameHandling.All, which should correctly serialize and deserialize the new Misc() value by emitting a "$type" object property along with the object itself. Unfortunately, for types such as enums (and others such as as int and long), this doesn't work because these are serialized as JSON primitives, with no opportunity to include a "$type" property.
The solution is, when serializing a dictionary with object values, to serialize object wrappers for primitive values that can encapsulate the type information, along the lines of this answer. Since you cannot modify any of your incoming objects and need to "inject" the proper wrappers, you can do this with a custom contract resolver that applies an appropriate item converter to the dictionary values:
public class UntypedToTypedValueContractResolver : DefaultContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static UntypedToTypedValueContractResolver instance;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static UntypedToTypedValueContractResolver() { instance = new UntypedToTypedValueContractResolver(); }
public static UntypedToTypedValueContractResolver Instance { get { return instance; } }
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
{
var contract = base.CreateDictionaryContract(objectType);
if (contract.DictionaryValueType == typeof(object) && contract.ItemConverter == null)
{
contract.ItemConverter = new UntypedToTypedValueConverter();
}
return contract;
}
}
class UntypedToTypedValueConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This converter should only be applied directly via ItemConverterType, not added to JsonSerializer.Converters");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var value = serializer.Deserialize(reader, objectType);
if (value is TypeWrapper)
{
return ((TypeWrapper)value).ObjectValue;
}
return value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (serializer.TypeNameHandling == TypeNameHandling.None)
{
Debug.WriteLine("ObjectItemConverter used when serializer.TypeNameHandling == TypeNameHandling.None");
serializer.Serialize(writer, value);
}
// Handle a couple of simple primitive cases where a type wrapper is not needed
else if (value is string)
{
writer.WriteValue((string)value);
}
else if (value is bool)
{
writer.WriteValue((bool)value);
}
else
{
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
if (contract is JsonPrimitiveContract)
{
var wrapper = TypeWrapper.CreateWrapper(value);
serializer.Serialize(writer, wrapper, typeof(object));
}
else
{
serializer.Serialize(writer, value);
}
}
}
}
public abstract class TypeWrapper
{
protected TypeWrapper() { }
[JsonIgnore]
public abstract object ObjectValue { get; }
public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}
public sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }
public TypeWrapper(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
public T Value { get; set; }
}
Then use it like:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
ContractResolver = UntypedToTypedValueContractResolver.Instance,
Converters = new [] { new StringEnumConverter() }, // If you prefer
};
var json = JsonConvert.SerializeObject(dict, Formatting.Indented, settings);
var dict2 = JsonConvert.DeserializeObject<Dictionary<string, object>>(json, settings);
Sample fiddle.
Finally, when using TypeNameHandling, do take note of this caution from the Newtonsoft docs:
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.
Assuming you have access to modify the object class, you can add the JsonCoverter attribute to the enum member of the class.
[JsonConverter(typeof(StringEnumConverter))]
Related
I have a generic type with 2 constructor each accepting 1 parameter with different types.
There is no problem with serialization. But when I try to deserialize, I get an error that Newtonsoft is Unable to find a constructor to use for this type.
How can I override Newtonsoft's default behavious and choose the constructor to be used for deserialization based on the property types?
I DO NOT have access to the Test class, so I can not change it in any way.
I wish to use another way not write a an entirely custom JsonConverter.
I saw this answer here that you can override the DefaultContractResolver. It works well to choose a different constructor, but I can not see how I can access the properties of the object I am trying to deserialize?
How can I choose a constructor based on the properties types of the object to be deserialized?
public class Test<T, U> where U : struct
{
public Test(T firstProperty)
{
FirstProperty = firstProperty;
}
public Test(U secondProperty)
{
SecondProperty = secondProperty;
}
public T FirstProperty { get; }
public U SecondProperty { get; }
}
There is no way to configure Json.NET to choose a constructor based on the presence or absence of certain properties in the JSON to be deserialized. It simply isn't implemented.
As a workaround, you can create a custom JsonConverter<Test<T, U>> that deserializes to some intermediate DTO that tracks the presence of both properties, and then chooses the correct constructor afterwards. Then you can create a custom contract resolver that applies the converter to all concrete types Test<T, U>.
The following converter and contract resolver perform this task:
class TestConverter<T, U> : JsonConverter where U : struct
{
// Here we make use of the {PropertyName}Specified pattern to track which properties actually got deserialized.
// https://stackoverflow.com/questions/39223335/how-to-force-newtonsoft-json-to-serialize-all-properties-strange-behavior-with/
class TestDTO
{
public T FirstProperty { get; set; }
[JsonIgnore] public bool FirstPropertySpecified { get; set; }
public U SecondProperty { get; set; }
[JsonIgnore] public bool SecondPropertySpecified { get; set; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var dto = serializer.Deserialize<TestDTO>(reader);
if (dto == null)
return null;
else if (dto.FirstPropertySpecified && !dto.SecondPropertySpecified)
return new Test<T, U>(dto.FirstProperty);
else if (!dto.FirstPropertySpecified && dto.SecondPropertySpecified)
return new Test<T, U>(dto.SecondProperty);
else
throw new InvalidOperationException(string.Format("Wrong number of properties specified for {0}", objectType));
}
public override bool CanConvert(Type objectType) => objectType == typeof(Test<T, U>);
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
public class TestContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Test<,>))
contract.Converter = (JsonConverter)Activator.CreateInstance(typeof(TestConverter<,>).MakeGenericType(objectType.GetGenericArguments()));
return contract;
}
}
Then use them e.g. as follows:
var json1 = #"{""FirstProperty"":""hello""}";
var json2 = #"{""SecondProperty"": 10101}";
IContractResolver resolver = new TestContractResolver(); // Cache this statically for best performance
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var test1 = JsonConvert.DeserializeObject<Test<string, long>>(json1, settings);
Assert.AreEqual(test1.FirstProperty, "hello");
var test2 = JsonConvert.DeserializeObject<Test<string, long>>(json2, settings);
Assert.AreEqual(test2.SecondProperty, 10101L);
Notes:
The converter throws an InvalidOperationException if the JSON does not contain exactly one of the two properties.
Feel free to modify this as per your requirements.
The converter does not implement serialization as your Test<T, U> type does not provide a method to track which property was initialized.
The converter does not attempt to handle subclasses of Test<T, U>.
Demo fiddle here.
Following the official documentation:
string jsonTypeNameAll = JsonConvert.SerializeObject(stockholder, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
Console.WriteLine(jsonTypeNameAll);
// {
// "$type": "Newtonsoft.Json.Samples.Stockholder, Newtonsoft.Json.Tests",
// "FullName": "Steve Stockholder",
// "Businesses": {
// "$type": "System.Collections.Generic.List`1[[Newtonsoft.Json.Samples.Business, Newtonsoft.Json.Tests]], mscorlib",
// "$values": [
// {
// "$type": "Newtonsoft.Json.Samples.Hotel, Newtonsoft.Json.Tests",
// "Stars": 4,
// "Name": "Hudson Hotel"
// }
// ]
// }
// }
I have copy pasted this code.
public static string Convert<T>(T cacheObject)
{
return JsonConvert.SerializeObject(cacheObject, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
}
However, if I call it with Convert(DateTime.Now) , I get a serialized DateTime string, without the type included:
What am I doing wrong?
TypeNameHandling works by adding a special, reserved "$type" property to JSON objects specifying the .Net type that was serialized. In addition, as explained in Newtonsoft's Serialization Guide, arrays will get nested inside a wrapper object that specifies the type:
Note that if TypeNameHandling or PreserveReferencesHandling has been enabled for JSON arrays on the serializer, then JSON arrays are wrapped in a containing object. The object will have the type name/reference properties and a $values property, which will have the collection data.
However, there is no such special case implemented when serializing JSON primitives. A .Net object that is serialized as a primitive will be emitted as a primitive without any wrapper added, even if TypeNameHandling is specified.
Thus, if you want to specify .Net type information for JSON primitives, you must create a wrapper object yourself. Some examples can be found in this answer to Deserialize Dictionary<string, object> with enum values in C# or this answer to JSON.net (de)serialize untyped property.
Following those answers, create the following wrapper:
public abstract class TypeWrapper
{
// Taken from this answer https://stackoverflow.com/a/38340375/3744182
// To https://stackoverflow.com/questions/38336390/deserialize-dictionarystring-object-with-enum-values-in-c-sharp
// By https://stackoverflow.com/users/3744182/dbc
protected TypeWrapper() { }
[JsonIgnore]
public abstract object ObjectValue { get; }
public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}
public sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }
public TypeWrapper(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
public T Value { get; set; }
}
Then modify your Convert<T>() as follows:
public static partial class JsonExtensions
{
static readonly IContractResolver globalResolver = new JsonSerializer().ContractResolver;
public static string Convert<T>(T cacheObject)
{
return JsonConvert.SerializeObject(ToTypeWrapperIfRequired(cacheObject), Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
}
public static T UnConvert<T>(string json)
{
var obj = JsonConvert.DeserializeObject<object>(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
if ((obj is TypeWrapper wrapper))
return (T)wrapper.ObjectValue;
return (T)obj;
}
static object ToTypeWrapperIfRequired<T>(T obj, IContractResolver resolver = null)
{
resolver = resolver ?? globalResolver;
if (obj == null)
return null;
// Type information is redundant for string or bool
if (obj is bool || obj is string)
return obj;
var contract = resolver.ResolveContract(obj.GetType());
if (contract is JsonPrimitiveContract)
return TypeWrapper.CreateWrapper(obj);
return obj;
}
}
And now you can convert to JSON and back as follows:
var json = JsonExtensions.Convert(obj);
var objBack = JsonExtensions.UnConvert<T>(json);
Notes:
A wrapper will be added to the object being serialized only if necessary -- i.e. if the .Net object is going to be serialized as a JSON primitive rather than a JSON object or array. In addition I don't add a wrapper if the incoming object is a string or a bool as these types can be inferred unambiguously from the JSON.
To do:
Decide whether the type of null-valued nullable structs needs to be preserved. If so they will need wrapping also.
Decide what should be done when round-tripping a JValue.
Serializing and deserializing an already-wrapped object result in it being unwrapped on deserialization, so you may want to throw an exception from Convert<T>() if the incoming object is a wrapper.
Do be aware that using TypeNameHandling can introduce security vulnerabilities into your application. For details see TypeNameHandling caution in Newtonsoft Json and External json vulnerable because of Json.Net TypeNameHandling auto?.
Also be aware that type name information isn't always portable between different .Net frameworks. See for example Serializing and deserializing in different frameworks #1378. You may need to write a custom ISerializationBinder in such situations.
Demo fiddle here.
Consider this interface and classes...
public interface IValueHolder{}
public class ValueHolder<TValue> : IValueHolder {
public ValueHolder(TValue value) => this.value = value;
public TValue value { get; }
public static implicit operator ValueHolder<TValue>(TValue value) => new ValueHolder<TValue>(value);
public static implicit operator TValue(ValueHolder<TValue> valueHolder) => valueHolder.value;
}
class ValueStorage : Dictionary<string, IValueHolder>{}
Note the implicit conversions to and from TValue.
The point of this code is I'm trying to store key/value pairs where the value can be any type, but without having boxing penalties. This is possible since I will always know the type when setting or retrieving the value from the storage. (That's why I didn't just use Dictionary<string, object>.)
Now consider this code...
var storage = new ValueStorage();
storage["UltimateQuestion"] = new ValueHolder<string>("What do you get when you multiply six by nine?");
storage["UltimateAnswer"] = new ValueHolder<int>(42);
var jsonSerializerSettings = new JsonSerializerSettings{
TypeNameHandling = TypeNameHandling.None,
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
var jsonString = JsonConvert.SerializeObject(storage, jsonSerializerSettings);
The result is this...
{
"UltimateQuestion": {
"value": "What do you get when you multiply six by nine?"
},
"UltimateAnswer": {
"value": 42
}
}
I was hoping with the implicit conversion to and from TValue, which are string and int respectively, it would give me this...
{
"UltimateQuestion": "What do you get when you multiply six by nine?",
"UltimateAnswer": 42
}
So how can I get ValueStorage<T> to serialize and deserialize as T? I don't mind writing custom converters (provided they're generic and can be based on TValue) or custom SettingsContractResolver subclasses, but I'm not sure where to start.
Update
The more I think about this, the more I think this isn't actually solvable. This is because while serialization is easy, the deserialization would need to know the types for TValue, which aren't stored in the JSON in the format that I want, therefore it can't be deserialized. (Even the original output above with the nested objects has the same issue. You need the type information for this to work in any capacity.)
However, as not to keep this question open, what about a converter that stores the type information for TValue instead of ValueHolder<TValue>?
e.g.
{
"UltimateQuestion": {
"$type": "[Whatever TValue is--string here]",
"value": "What do you get when you multiply six by nine?"
},
"UltimateAnswer": {
"$type": "[Whatever TValue is--int here]",
"value": 42
}
}
That way the converter could reconstruct ValueHolder<TValue> instances during deserialization. It's not perfect, but would at least allow proper deserialization without exposing the ValueHolder framework.
You can write a converter (see https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm).
public class ValueHolderJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, ((IValueHolder)value).value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return Activator.CreateInstance(objectType, serializer.Deserialize(reader));
}
public override bool CanConvert(Type objectType)
{
return typeof(IValueHolder).IsAssignableFrom(objectType);
}
}
Please not that I added a non-generic "value" property to your interface to get the values regardless of the actual generic type without needing to use reflection:
public interface IValueHolder{
object value { get; }
}
public class ValueHolder<TValue> : IValueHolder
{
// as before
object IValueHolder.value => (object)value;
}
To use it you create a new JsonSerializerSettings class (or append your existing one):
var settings = new JsonSerializerSettings {
Converters = {
new ValueHolderJsonConverter()
};
And provide it as parameter to SerializeObject and DeserializeObject.
I have an C# MVC application which stores data as JSON strings in an XML document and also in MySQL DB Tables.
Recently I have received the requirement to store JSON strings in MySQL Database fields, to be converted into C# objects via Newtonsoft.Json, so I decided to implement a TypeConverter to convert JSON strings into custom C# Models.
Unfortunately I cannot use the following command anywhere in my solution to deserialize my JSON strings when the TypeConverter attribute is added to my C# Model:
JsonConvert.DeserializeObject<Foo>(json);
Removing the attribute resolve the issue however this prevents me from converting MySQL DB fields into custom C# objects.
Here is my C# Model with the TypeConverter Attribute added:
using System.ComponentModel;
[TypeConverter(typeof(FooConverter))]
public class Foo
{
public bool a { get; set; }
public bool b { get; set; }
public bool c { get; set; }
public Foo(){}
}
Here is my TypeConverter Class:
using Newtonsoft.Json;
using System;
using System.ComponentModel;
public class FooConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
string s = value.ToString().Replace("\\","");
Foo f = JsonConvert.DeserializeObject<Foo>(s);
return f;
}
return base.ConvertFrom(context, culture, value);
}
}
}
As soon as I add the attribute to the Foo Class I receive the following error:
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'Models.Foo' because the type requires a JSON string value to deserialize correctly.
To fix this error either change the JSON to a JSON string value or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
I am using the following string (which works perfectly without adding the TypeConverter Attribute):
"{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}"
Not sure what's going on here, any ideas?
Many Thanks!!!
UPDATE
I have have discovered that I also have issues with actions on MVC API Controllers that accept the Test Class with Foo as a property or on controllers that accept Foo as an object when the TypeConverter attribute is added to the Foo Class.
Here is an example of a test controller which has issues:
public class TestController : ApiController
{
[AcceptVerbs("POST", "GET")]
public void PostTestClass(TestClass t)
{
// Returns null when TypeConverter attribute is added to the Foo Class
return t.Foo;
}
AcceptVerbs("POST", "GET")]
public void PostFooObj(Foo f)
{
// Returns null when TypeConverter attribute is added to the Foo Class
return f;
}
}
The TypeConverter may be causing issues overriding the WebAPI model binding and returns null when either action above receives JSON via AJAX with the following structure:
// eg. PostTestClass(TestClass T)
{'Foo': {'a': false,'b': true,'c': false}};
// eg. PostFooObj(Foo f)
{'a': false,'b': true,'c': false}
When the TypeConverter Attribute is added to the Foo Class, the following method on the FooConverter TypeConverter class is called as soon the route is found:
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
The ConvertFrom method on the FooConverter TypeController is NOT called by the ApiController's action, which may be the cause of the issue.
Again it's a similar situation, where the controllers actions will work fine without the TypeConverter Attribute.
Any further help greatly appreciated!!
Many thanks.
There are a few things going on here. First, a preliminary issue: even with no TypeConverter applied, your JSON does not correspond to your class Foo, it corresponds to some container class that contains a Foo property, for instance:
public class TestClass
{
public Foo Foo { get; set; }
}
I.e. given your JSON string, the following will not work:
var json = "{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}";
var foo = JsonConvert.DeserializeObject<Foo>(json);
But the following will:
var test = JsonConvert.DeserializeObject<TestClass>(json);
I suspect this is simply a mistake in the question, so I'll assume you are looking to deserialize a class contain a property Foo.
The main problem you are seeing is that Json.NET will try to use a TypeConverter if one is present to convert a class to be serialized to a JSON string. From the docs:
Primitive Types
.Net: TypeConverter (convertible to String)
JSON: String
But in your JSON, Foo is not a JSON string, it is a JSON object, thus deserialization fails once the type converter is applied. An embedded string would look like this:
{"Foo":"{\"a\":true,\"b\":false,\"c\":false}"}
Notice how all the quotes have been escaped. And even if you changed your JSON format for Foo objects to match this, your deserialization would still fail as the TypeConverter and Json.NET try to call each other recursively.
Thus what you need to do is to globally disable use of the TypeConverter by Json.NET and fall back to default serialization while retaining use of the TypeConverter in all other situations. This is a bit tricky since there is no Json.NET attribute you can apply to disable use of type converters, instead you need a special contract resolver plus a special JsonConverter to make use of it:
public class NoTypeConverterJsonConverter<T> : JsonConverter
{
static readonly IContractResolver resolver = new NoTypeConverterContractResolver();
class NoTypeConverterContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (typeof(T).IsAssignableFrom(objectType))
{
var contract = this.CreateObjectContract(objectType);
contract.Converter = null; // Also null out the converter to prevent infinite recursion.
return contract;
}
return base.CreateContract(objectType);
}
}
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Deserialize(reader, objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Serialize(writer, value);
}
}
And use it like:
[TypeConverter(typeof(FooConverter))]
[JsonConverter(typeof(NoTypeConverterJsonConverter<Foo>))]
public class Foo
{
public bool a { get; set; }
public bool b { get; set; }
public bool c { get; set; }
public Foo() { }
}
public class FooConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
string s = value.ToString();
//s = s.Replace("\\", "");
Foo f = JsonConvert.DeserializeObject<Foo>(s);
return f;
}
return base.ConvertFrom(context, culture, value);
}
}
Example fiddle.
Finally, you should probably also implement the ConvertTo method in your type converter, see How to: Implement a Type Converter.
The easy way to avoid this behavior is to remove OR from conversion check
i.e. remove || destinationType == typeof(string)
An example below..
public class DepartmentBindModelConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(DepartmentViewModel); // Removed || destinationType == typeof(string), to allow newtonsoft json convert model with typeconverter attribute
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value == null)
return null;
if (destinationType == typeof(DepartmentViewModel) && value is DepartmentBindModel)
{
var department = (DepartmentBindModel) value;
return new DepartmentViewModel
{
Id = department.Id,
Name = department.Name,
GroupName = department.GroupName,
ReturnUrl = department.ReturnUrl
};
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
If you have a struct not a class, then the accepted answer will still go into an infinite recursion when trying to (de)serialize Nullable<Foo>.
To avoid modify CreateContract as follows:
protected override JsonContract CreateContract(Type objectType)
{
if (typeof(T).IsAssignableFrom(objectType)
|| Nullable.GetUnderlyingType(objectType) != null && typeof(T).IsAssignableFrom(Nullable.GetUnderlyingType(objectType)))
{
var contract = this.CreateObjectContract(objectType);
contract.Converter = null; // Also null out the converter to prevent infinite recursion.
return contract;
}
return base.CreateContract(objectType);
}
Why does my serialized JSON end up as
{"Gender":1,"Dictionary":{"Male":100,"Female":200}}
i.e. why do the enums serialize to their value, but when they form they key to the dictionary they are converted to their key?
How do I make them be ints in the dictionary, and why isn't this the default behaviour?
I'd expect the following output
{"Gender":1,"Dictionary":{"0":100,"1":200}}
My code:
public void foo()
{
var testClass = new TestClass();
testClass.Gender = Gender.Female;
testClass.Dictionary.Add(Gender.Male, 100);
testClass.Dictionary.Add(Gender.Female, 200);
var serializeObject = JsonConvert.SerializeObject(testClass);
// serializeObject == {"Gender":1,"Dictionary":{"Male":100,"Female":200}}
}
public enum Gender
{
Male = 0,
Female = 1
}
public class TestClass
{
public Gender Gender { get; set; }
public IDictionary<Gender, int> Dictionary { get; set; }
public TestClass()
{
this.Dictionary = new Dictionary<Gender, int>();
}
}
}
The reason why Gender enum is serialized to its value when used as property value, but it is serialized to its string representation when used as dictionary key is the following:
When used as property value JSON.NET serializer first writes the property name and after that the property value. For the example you posted, JSON.NET will write "Gender" as property name (notice that it writes a string), than will try to resolve the value of the property. The value of the property is of type enum which JSON.NET handles as Int32 and it writes the number representation of the enum
When serializing the dictionary, the keys are written as property names, so the JSON.NET serializer writes the string representation of the enum. If you switch the types of the keys and values in the dictionary (Dictionary<int, Gender> instead of Dictionary<Gender, int>, you'll verify that the enum will be serialized with its Int32 representation.
To achieve what you want with the example you posted, you'll need to write custom JsonConverter for the Dictionary property. Something like this:
public class DictionaryConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var dictionary = (Dictionary<Gender, int>) value;
writer.WriteStartObject();
foreach (KeyValuePair<Gender, int> pair in dictionary)
{
writer.WritePropertyName(((int)pair.Key).ToString());
writer.WriteValue(pair.Value);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var maleValue = int.Parse(jObject[((int) Gender.Male).ToString()].ToString());
var femaleValue = int.Parse(jObject[((int)Gender.Female).ToString()].ToString());
(existingValue as Dictionary<Gender, int>).Add(Gender.Male, maleValue);
(existingValue as Dictionary<Gender, int>).Add(Gender.Female, femaleValue);
return existingValue;
}
public override bool CanConvert(Type objectType)
{
return typeof (IDictionary<Gender, int>) == objectType;
}
}
and decorate the property in the TestClass:
public class TestClass
{
public Gender Gender { get; set; }
[JsonConverter(typeof(DictionaryConverter))]
public IDictionary<Gender, int> Dictionary { get; set; }
public TestClass()
{
this.Dictionary = new Dictionary<Gender, int>();
}
}
When calling the following line for serialization:
var serializeObject = JsonConvert.SerializeObject(testClass);
you'll get the desired output:
{"Gender":1,"Dictionary":{"0":100,"1":200}}
I often find myself facing this issue so I did a JsonConverter that can handle any kind of dictionnary with an Enum type as key:
public class DictionaryWithEnumKeyConverter<T, U> : JsonConverter where T : System.Enum
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var dictionary = (Dictionary<T, U>)value;
writer.WriteStartObject();
foreach (KeyValuePair<T, U> pair in dictionary)
{
writer.WritePropertyName(Convert.ToInt32(pair.Key).ToString());
serializer.Serialize(writer, pair.Value);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var result = new Dictionary<T, U>();
var jObject = JObject.Load(reader);
foreach (var x in jObject)
{
T key = (T) (object) int.Parse(x.Key); // A bit of boxing here but hey
U value = (U) x.Value.ToObject(typeof(U));
result.Add(key, value);
}
return result;
}
public override bool CanConvert(Type objectType)
{
return typeof(IDictionary<T, U>) == objectType;
}
}
NB: This will not handle Dictionnary<Enum, Dictionnary<Enum, T>
The answer from Ilija Dimov covers why it happens, but the suggested converter only works for this specific case.
Here's a reusable converter which formats enum keys as their value, for any enum key in any Dictionary<,>/IDictionary<,> field:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Newtonsoft.Json;
/// <summary>A Json.NET converter which formats enum dictionary keys as their underlying value instead of their name.</summary>
public class DictionaryNumericEnumKeysConverter : JsonConverter
{
public override bool CanRead => false; // the default converter handles numeric keys fine
public override bool CanWrite => true;
public override bool CanConvert(Type objectType)
{
return this.TryGetEnumType(objectType, out _);
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
throw new NotSupportedException($"Reading isn't implemented by the {nameof(DictionaryNumericEnumKeysConverter)} converter."); // shouldn't be called since we set CanRead to false
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
// handle null
if (value is null)
{
writer.WriteNull();
return;
}
// get dictionary & key type
if (value is not IDictionary dictionary || !this.TryGetEnumType(value.GetType(), out Type? enumType))
throw new InvalidOperationException($"Can't parse value type '{value.GetType().FullName}' as a supported dictionary type."); // shouldn't be possible since we check in CanConvert
Type enumValueType = Enum.GetUnderlyingType(enumType);
// serialize
writer.WriteStartObject();
foreach (DictionaryEntry pair in dictionary)
{
writer.WritePropertyName(Convert.ChangeType(pair.Key, enumValueType).ToString()!);
serializer.Serialize(writer, pair.Value);
}
writer.WriteEndObject();
}
/// <summary>Get the enum type for a dictionary's keys, if applicable.</summary>
/// <param name="objectType">The possible dictionary type.</param>
/// <param name="keyType">The dictionary key type.</param>
/// <returns>Returns whether the <paramref name="objectType"/> is a supported dictionary and the <paramref name="keyType"/> was extracted.</returns>
private bool TryGetEnumType(Type objectType, [NotNullWhen(true)] out Type? keyType)
{
// ignore if type can't be dictionary
if (!objectType.IsGenericType || objectType.IsValueType)
{
keyType = null;
return false;
}
// ignore if not a supported dictionary
{
Type genericType = objectType.GetGenericTypeDefinition();
if (genericType != typeof(IDictionary<,>) && genericType != typeof(Dictionary<,>))
{
keyType = null;
return false;
}
}
// extract key type
keyType = objectType.GetGenericArguments().First();
if (!keyType.IsEnum)
keyType = null;
return keyType != null;
}
}
You can enable it on a specific field:
[JsonConverter(typeof(DictionaryNumericEnumKeysConverter))]
public IDictionary<Gender, int> Dictionary { get; set; }
Or enable it for all dictionaries with enum keys:
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter>
{
new DictionaryNumericEnumKeysConverter()
}
};
If you're asking why this is the default behavior as a matter of compu-prudence, there are at least a couple of really good reasons IMO, but both ultimately have to do with interoperability, i.e., the likelihood that your JSON data is getting exchanged with another system outside your own and/or that uses another language and/or doesn't share your C# code.
First, enum number values in C# can change in different assembly versions if you insert more and don't specify numbers yourself. Granted you specify the numbers here, but many people do not. Plus, even if you do specify the numbers, you're now committing to these values as part of your API contract for all time, or else make a breaking change. And even if the only consumer of this JSON data is your own code, unless you use some kind of automatic Typescript generator based on your C# (which I do and should be done more often!!) you have at least two places you'd need to update if you want to change these numbers. So by serializing using the enum name as the key rather than the number value, consumers can remain oblivious to the numeric values and have no possibility of breaking when the enum numbers change.
Second, the string values are just more user friendly to consumers. Anyone looking at the raw JSON data would likely be able to get a good understanding of the contents of the dictionary without the need for any documentation. Better still, the consumer doesn't have to be keeping independent track of the number values for each key in their own code, which would just be another opportunity for error on their end.
So as others noted, you can change this behavior if you want, but in terms of picking a default behavior that captures the practice of the most people and the original intent behind JSON, this seems like the right approach.