When serializing arbitrary data via JSON.NET, any property that is null is written to the JSON as
"propertyName" : null
This is correct, of course.
However I have a requirement to automatically translate all nulls into the default empty value, e.g. null strings should become String.Empty, null int?s should become 0, null bool?s should be false, and so on.
NullValueHandling is not helpful, since I dont want to Ignore nulls, but neither do I want to Include them (Hmm, new feature?).
So I turned to implementing a custom JsonConverter.
While the implementation itself was a breeze, unfortunately this still didnt work - CanConvert() is never called for a property that has a null value, and therefore WriteJson() is not called either. Apparently nulls are automatically serialized directly into null, without the custom pipeline.
For example, here is a sample of a custom converter for null strings:
public class StringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(string).IsAssignableFrom(objectType);
}
...
public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
string strValue = value as string;
if (strValue == null)
{
writer.WriteValue(String.Empty);
}
else
{
writer.WriteValue(strValue);
}
}
}
Stepping through this in the debugger, I noted that neither of these methods are called for properties that have a null value.
Delving into JSON.NET's sourcecode, I found that (apparently, I didnt go into a lot of depth) there is a special case checking for nulls, and explictly calling .WriteNull().
For what it's worth, I did try implementing a custom JsonTextWriter and overriding the default .WriteNull() implementation...
public class NullJsonWriter : JsonTextWriter
{
...
public override void WriteNull()
{
this.WriteValue(String.Empty);
}
}
However, this can't work well, since the WriteNull() method knows nothing about the underlying datatype. So sure, I can output "" for any null, but that doesnt work well for e.g. int, bool, etc.
So, my question - short of converting the entire data structure manually, is there any solution or workaround for this?
Okay, I think I've come up with a solution (my first solution wasn't right at all, but then again I was on the train). You need to create a special contract resolver and a custom ValueProvider for Nullable types. Consider this:
public class NullableValueProvider : IValueProvider
{
private readonly object _defaultValue;
private readonly IValueProvider _underlyingValueProvider;
public NullableValueProvider(MemberInfo memberInfo, Type underlyingType)
{
_underlyingValueProvider = new DynamicValueProvider(memberInfo);
_defaultValue = Activator.CreateInstance(underlyingType);
}
public void SetValue(object target, object value)
{
_underlyingValueProvider.SetValue(target, value);
}
public object GetValue(object target)
{
return _underlyingValueProvider.GetValue(target) ?? _defaultValue;
}
}
public class SpecialContractResolver : DefaultContractResolver
{
protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
{
if(member.MemberType == MemberTypes.Property)
{
var pi = (PropertyInfo) member;
if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof (Nullable<>))
{
return new NullableValueProvider(member, pi.PropertyType.GetGenericArguments().First());
}
}
else if(member.MemberType == MemberTypes.Field)
{
var fi = (FieldInfo) member;
if(fi.FieldType.IsGenericType && fi.FieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
return new NullableValueProvider(member, fi.FieldType.GetGenericArguments().First());
}
return base.CreateMemberValueProvider(member);
}
}
Then I tested it using:
class Foo
{
public int? Int { get; set; }
public bool? Boolean { get; set; }
public int? IntField;
}
And the following case:
[TestFixture]
public class Tests
{
[Test]
public void Test()
{
var foo = new Foo();
var settings = new JsonSerializerSettings { ContractResolver = new SpecialContractResolver() };
Assert.AreEqual(
JsonConvert.SerializeObject(foo, Formatting.None, settings),
"{\"IntField\":0,\"Int\":0,\"Boolean\":false}");
}
}
Hopefully this helps a bit...
Edit – Better identification of the a Nullable<> type
Edit – Added support for fields as well as properties, also piggy-backing on top of the normal DynamicValueProvider to do most of the work, with updated test
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.
I am consuming an API that is supposed to return an object, like
{
"some_object": {
"some_field": "some value"
}
}
when that object is null, I would expect
{
"some_object": null
}
or
{
"some_object": {}
}
But what they send me is
{
"some_object": []
}
...even though it's never an array.
When using
JsonSerializer.Deserialize<MyObject>(myJson, myOptions)
an exception is thrown when [] appears where null is expected.
Can I selectively ignore this exception?
My current way of handling this is to read the json and fix it with a regex before deserialization.
I prefer to use System.Text.Json, and not introduce other dependencies, if possible.
This solution uses a custom JsonConverter in System.Text.Json.
If some_object is an array then it will return an empty object (or null if you prefer), and no exception will be thrown. Otherwise it will correctly deserialize the json.
public class EmptyArrayToObjectConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
var rootElement = JsonDocument.ParseValue(ref reader);
// if its array return new instance or null
if (reader.TokenType == JsonTokenType.EndArray)
{
// return default(T); // if you want null value instead of new instance
return (T)Activator.CreateInstance(typeof(T));
}
else
{
var text = rootElement.RootElement.GetRawText();
return JsonSerializer.Deserialize<T>(text, options);
}
}
public override bool CanConvert(Type typeToConvert)
{
return true;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize<T>(writer, value, options);
}
}
Decorate your property with the JsonConverter attribute. Your class might look something like this:
public class MyObject
{
[JsonPropertyAttribute("some_object")]
[JsonConverter(typeof(EmptyArrayToObjectConverter<SomeObject>))]
public SomeObject SomeObject { get; set; }
...
}
You can use [OnError] attribute to conditionally suppress exception related with a particular member. Let me try to explain it with an example.
The example class which represents JSON file. It contains a nested class SomeObject.
public class MyObject
{
public int TemperatureCelsius { get; set; }
public SomeObject SomeObject { get; set; }
[OnError]
internal void OnError(StreamingContext context, ErrorContext errorContext)
{
//You can check if exception is for a specific member then ignore it
if(errorContext.Member.ToString().CompareTo("SomeObject") == 0)
{
errorContext.Handled = true;
}
}
}
public class SomeObject
{
public int High { get; set; }
public int Low { get; set; }
}
If sample JSON stream/file contains text as:
{
"TemperatureCelsius": 25,
"SomeObject": []
}
then exception is handled and suppressed as exception is raised for SomeObject member. The SomeObject member is set as null.
If input JSON stream/file contains text as:
{
"TemperatureCelsius": 25,
"SomeObject":
{
"Low": 1,
"High": 1001
}
}
then object is serialized properly with SomeObject representing expected value.
Here is a solution using a custom JsonConverter and Newtonsoft.Json.
This will set SomeObject to null in MyObject if it is an array. You can return a new instance of SomeObject instead by returning (T)Activator.CreateInstance(typeof(T)).
public class ArrayToObjectConverter<T> : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
// this returns null (default(SomeObject) in your case)
// if you want a new instance return (T)Activator.CreateInstance(typeof(T)) instead
return default(T);
}
return token.ToObject<T>();
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return true; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Note that Newtonsoft.Json ignores CanConvert (since the property is decorated with JsonConverter attribute) it assumes it can write and convert so does not call these methods (you could return false or throw NotImplementedException instead and it will still serialize/deserialize).
In your model, decorate some_object with the JsonConvert attribute. Your class might look something like this:
public class MyObject
{
[JsonProperty("some_object")]
[JsonConverter(typeof(ArrayToObjectConverter<SomeObject>))]
public SomeObject SomeObject { get; set; }
}
I know you said you'd prefer to use System.Text.Json but this might be useful for others using Json.Net.
Update: I did create a JsonConverter solution using System.Text.Json and it is here.
Solutions above work fine, I'll give mine for .NET Core 3 and above, which is just a reader, not a writer (no need). The source json, is buggy, and gives an empty array, when it should be 'null'. So, this custom converter does the correction work.
so: "myproperty":{"lahdidah": 1} is [] when it actually should be: "myproperty": null
Note, the TrySkip, we don't need to eat bogus elements.
public sealed class JsonElementOrArrayFixerConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
reader.TrySkip();
return default;
}
return JsonSerializer.Deserialize<T>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
Exception handling is a pet peeve of mine. And I have two articles from other people that I link often on the mater:
https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/
https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET
I consider them required reading and use them as basis of any discussion on the topic.
As a general rule, Exception should never be ignored. At best they should be caught and published. At worst, they should not even be caught. It is too easy to cause followup issues and make debugging impossible to be careless or overly agressive.
That being said, in this case (deserialisation) some Exceptions could be classified as either a Exogenous or Vexing Exception. Wich are the kind you catch. And with Vexing, you might even swallow them (like TryParse() kinda does).
Usually you want to catch as specific as possible. Sometimes however you got a very wide range of Exceptions with no decent common ancestors, but shared handling. Luckily I once wrote this attempt to replicate TryParse() for someone stuck on 1.1:
//Parse throws ArgumentNull, Format and Overflow Exceptions.
//And they only have Exception as base class in common, but identical handling code (output = 0 and return false).
bool TryParse(string input, out int output){
try{
output = int.Parse(input);
}
catch (Exception ex){
if(ex is ArgumentNullException ||
ex is FormatException ||
ex is OverflowException){
//these are the exceptions I am looking for. I will do my thing.
output = 0;
return false;
}
else{
//Not the exceptions I expect. Best to just let them go on their way.
throw;
}
}
//I am pretty sure the Exception replaces the return value in exception case.
//So this one will only be returned without any Exceptions, expected or unexpected
return true;
}
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))]
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);
}
Now Im writing a universal method for loading configuration data from XML. A lot of parameters in my case are stored in node attributes, so I decided to write a universal method for attribute reading:
private static T ReadAttribute<T>(XElement Element,string AttributeName)
{
var attrib = Element.Attribute(AttributeName);
if (attrib != null)
{
return attrib.Value; // off cource error is in this line !
}
else
{
return default(T);
}
}
This method should try to read attribute with specified name and if this attribute missed it should return default value for attribute type. Attribute type is specified by T.
As it shown in comment above my problem is that I cant universally convert string value into specific type. Actually I plan use int, double and two enum types as T.
What way I should act in this situation? How I should convert string value into T type?
Thanks in advance!
You can use the Convert.ChangeType. It does basically what you want. But it's a conversion not a cast, well, not just a cast.
return (T)Convert.ChangeType(attrib.Value, typeof(T), CultureInfo.InvariantCulture);
The reason why you can simply cast a string to some arbitrary type is that the type system doesn't allow that. However Convert.ChangeType returns object which could be any type and therefore the cast is allowed.
The CultureInfo.InvariantCulture is important becuase XML content isn't shouldn't be encoded/decoded using different cultures. The XmlConvert class should be used if working with XML however it doesn't have a handy generic method like XmlConvert.ChangeType.
The XAttribute class has many explicit user-defined casts that map to the XmlConvert class. However, you cannot simply use these with a unconstrained type parameter T and expect the same result.
To make matters worse, XML and Convert doesn't actually play nice. So if you're really serious about this you would write something like this to handle the conversions.
static T ConvertTo<T>(XAttribute attr)
{
object value;
switch (Type.GetTypeCode(typeof(T)))
{
case TypeCode.Boolean: value = XmlConvert.ToBoolean(attr.Value); break;
case TypeCode.Int32: value = XmlConvert.ToInt32(attr.Value); break;
case TypeCode.DateTime: value = XmlConvert.ToDateTime(attr.Value); break;
// Add support for additional TypeCode values here...
default:
throw new ArgumentException(string.Format("Unsupported destination type '{0}'.", typeof(T)));
}
return (T)value;
}
I would go with the TypeConverter stuff. It's basically a class that does conversions to/from values and cultures. The primary difference between a TypeConverter and Convert.ChangeType is that the later requires the IConvertible interface on the source type, while TypeConverters can work with any objects.
I've created a helper class for this, since I often store different configuration objects in xml-files. That's also why it's hardcoded to convert to/from CultureInfo.InvariantCulture.
public static class TypeConversion {
public static Object Convert(Object source, Type targetType) {
var sourceType = source.GetType();
if (targetType.IsAssignableFrom(sourceType))
return source;
var sourceConverter = TypeDescriptor.GetConverter(source);
if (sourceConverter.CanConvertTo(targetType))
return sourceConverter.ConvertTo(null, CultureInfo.InvariantCulture, source, targetType);
var targetConverter = TypeDescriptor.GetConverter(targetType);
if (targetConverter.CanConvertFrom(sourceType))
return targetConverter.ConvertFrom(null, CultureInfo.InvariantCulture, source);
throw new ArgumentException("Neither the source nor the target has a TypeConverter that supports the requested conversion.");
}
public static TTarget Convert<TTarget>(object source) {
return (TTarget)Convert(source, typeof(TTarget));
}
}
It's fully possible to create your own TypeConverter to handle system types, like System.Version (which doesnt implement IConvertible) to support conversions like from strings containing a version number ("a.b.c.d") to an actual Version object.
public class VersionTypeConverter : TypeConverter {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
var s = value as string;
if (s != null)
return new Version(s);
return base.ConvertFrom(context, culture, value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
if (destinationType == typeof(string))
return true;
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
var v = value as Version;
if (v != null && destinationType == typeof(string)) {
return v.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
To actually use this provider you need to registered it during application startup, using TypeDescriptor.AddProvider, passing in a custom TypeDescriptionProvider, and typeof(Version). This needs to return a custom CustomTypeDescriptor in the TypeDescriptorProvider.GetTypeDescriptor method, and the descriptor needs to override GetConverter to return a new instance of VersionTypeConverter. Easy. ;)
Built-in methods won't help if T is a type defined by yourself. Let's say the xml looks like:
//some other segments
<Book Name="Good book" Price="20" Author="Jack" />
And you T is the class Book that looks like:
class Book
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Author { get; set; }
//maybe some other properties
}
There is no magic to convert the XElement to an instance of Book automatically, you need to implement it yourself. An easy and general implementation is something like this:
interface IXElementConvertible
{
void LoadFrom(XElement element);
}
class Book : IXElementConvertible
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Author { get; set; }
public void LoadFrom(XElement element)
{
this.Name = element.Attribute("Name").Value;
//blabla
}
}
And you need to modify your method:
private static T ReadAttribute<T>(XElement Element,string AttributeName)
where T : IXElementConvertible, new()
{
T t = new T();
t.LoadFrom(element);
//just an example here, not the complete implementation
}
I think you should do the following check in you code:
if (attrib.Value is T)
{
...
}