Newtonsoft.JSON cannot convert model with TypeConverter attribute
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.
JSON.NET not utilizing TypeConverter for Dictionary key
It turns out that this was a combination of two different issues:
- As explained in @dbc's answer, I was using
&&
instead of||
inTimeConverter.CanConvertFrom()
. - I was using version 9 of Json.NET, but in .NET Core,
TypeConverter
support was not implemented until version 10.
Using ||
and upgrading to Json.NET 12 solved my issue.
Can I use TypeConverter attribute on single property of a model for Json serializer?
Checking for [TypeConverter(typeof(...))]
attributes applied to members is not implemented out of the box in Json.NET. You could, however, create a custom JsonConverter
that wraps an arbitrary TypeConverter
, then apply that to your model using JsonConverterAttribute
.
First, define the following JsonConverter
:
public class TypeConverterJsonConverter : JsonConverter
{
readonly TypeConverter converter;
public TypeConverterJsonConverter(Type typeConverterType) : this((TypeConverter)Activator.CreateInstance(typeConverterType)) { }
public TypeConverterJsonConverter(TypeConverter converter)
{
if (converter == null)
throw new ArgumentNullException();
this.converter = converter;
}
public override bool CanConvert(Type objectType)
{
return converter.CanConvertFrom(typeof(string)) && converter.CanConvertTo(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var tokenType = reader.SkipComments().TokenType;
if (tokenType == JsonToken.Null)
return null;
if (!tokenType.IsPrimitive())
throw new JsonSerializationException(string.Format("Token {0} is not primitive.", tokenType));
var s = (string)JToken.Load(reader);
return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = converter.ConvertToInvariantString(value);
writer.WriteValue(s);
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
public static bool IsPrimitive(this JsonToken tokenType)
{
switch (tokenType)
{
case JsonToken.Integer:
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
case JsonToken.Undefined:
case JsonToken.Null:
case JsonToken.Date:
case JsonToken.Bytes:
return true;
default:
return false;
}
}
}
Then apply it to your model as follows:
public class JsonModel
{
[JsonConverter(typeof(TypeConverterJsonConverter), typeof(CidNumberConvertor))]
[TypeConverter(typeof(CidNumberConvertor))]
[JsonProperty("cid_number")]
public Cid CidNumber;
[JsonConverter(typeof(TypeConverterJsonConverter), typeof(CidHexaConvertor))]
[TypeConverter(typeof(CidHexaConvertor))]
[JsonProperty("cid_hexa")]
public Cid CidHexa;
[JsonProperty("cid_default")]
public Cid CidDefault;
}
Notes:
Applying a
JsonConverter
overrides use of the global defaultTypeConverter
forCid
.The
JsonConverterAttribute(Type,Object[])
constructor is used to pass the specificTypeConverter
type to the constructor ofTypeConverterJsonConverter
as an argument.In production code, I assume those are properties not fields.
Sample fiddle #1 here. (In the absence of a mcve I had to create a stub implementation of Cid
.)
Alternatively, if you have many properties for which you want to use an applied TypeConverter
when serializing to JSON, you can create a custom ContractResolver
that instantiates and applies TypeConverterJsonConverter
automatically:
public class PropertyTypeConverterContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.Converter == null)
{
// Can more than one TypeConverterAttribute be applied to a given member? If so,
// what should we do?
var attr = property.AttributeProvider.GetAttributes(typeof(TypeConverterAttribute), false)
.OfType<TypeConverterAttribute>()
.SingleOrDefault();
if (attr != null)
{
var typeConverterType = GetTypeFromName(attr.ConverterTypeName, member.DeclaringType.Assembly);
if (typeConverterType != null)
{
var jsonConverter = new TypeConverterJsonConverter(typeConverterType);
if (jsonConverter.CanConvert(property.PropertyType))
{
property.Converter = jsonConverter;
// MemberConverter is obsolete or removed in later versions of Json.NET but
// MUST be set identically to Converter in earlier versions.
property.MemberConverter = jsonConverter;
}
}
}
}
return property;
}
static Type GetTypeFromName(string typeName, Assembly declaringAssembly)
{
// Adapted from https://referencesource.microsoft.com/#System/compmod/system/componentmodel/PropertyDescriptor.cs,1c1ca94869d17fff
if (string.IsNullOrEmpty(typeName))
{
return null;
}
Type typeFromGetType = Type.GetType(typeName);
Type typeFromComponent = null;
if (declaringAssembly != null)
{
if ((typeFromGetType == null) ||
(declaringAssembly.FullName.Equals(typeFromGetType.Assembly.FullName)))
{
int comma = typeName.IndexOf(',');
if (comma != -1)
typeName = typeName.Substring(0, comma);
typeFromComponent = declaringAssembly.GetType(typeName);
}
}
return typeFromComponent ?? typeFromGetType;
}
}
Then use it as follows:
// Cache statically for best performance.
var resolver = new PropertyTypeConverterContractResolver();
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
var root2 = JsonConvert.DeserializeObject<JsonModel>(json, settings);
Notes:
You may want to cache the contract resolver for best performance.
JsonProperty.MemberConverter
is obsolete in the current version of Json.NET but must be set identically toJsonProperty.Converter
in earlier versions.
Sample fiddle #2 here.
Newtonsoft.Json - DeserializeObject throws when deserializing custom type: Error converting value somestring to type CustomType
Because your class implements IConvertible
, the JsonSerializerInternalReader
is apparently attempting to call Convert.ChangeType
instead of using the TypeConverter
you supplied. There is a comment at line 984 of the source code stating that Convert.ChangeType
does not work for a custom IConvertible
, so the author is ostensibly aware of the issue:
if (contract.IsConvertable)
{
JsonPrimitiveContract primitiveContract = (JsonPrimitiveContract)contract;
...
// this won't work when converting to a custom IConvertible
return Convert.ChangeType(value, contract.NonNullableUnderlyingType, culture);
}
You can work around the problem by implementing a custom JsonConverter
for your FriendlyUrl
class:
public class FriendlyUrlJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(FriendlyUrl);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new FriendlyUrl((string)reader.Value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((FriendlyUrl)value).ToString());
}
}
To use the JsonConverter
, simply add a [JsonConverter]
attribute to your FriendlyUrl
class in the same way that you did for [TypeConverter]
. You can then remove the [TypeConverter]
attribute, unless you need it for some other purpose. (Json.Net's DefaultContractResolver
looks for a JsonConverter
first when resolving types, so it will take precedence over TypeConverter
.)
[JsonConverter(typeof(FriendlyUrlJsonConverter))]
public class FriendlyUrl : IEquatable<FriendlyUrl>, IConvertible
{
...
}
Fiddle: https://dotnetfiddle.net/HyaQWb
Why does an F# Discriminated Union fails to have its TypeConverter respected by JSON.NET but other types can?
Your problem is that Json.NET has its own built-in converter for discriminated unions, DiscriminatedUnionConverter
. Any applicable JsonConverter
will always supersede an applied TypeConverter
.
A built-in converter can be disabled by providing your own, alternate, JsonConverter
, either in settings or via an applied JsonConverterAttribute
. You have already created a converter that correctly converts your type C
, but if you would prefer to fall back to the applied TypeConverter
, you can create a JsonConverter
that does nothing and falls back on default serialization by returning false
from CanRead
and CanWrite
:
type NoConverter<'a> () =
inherit JsonConverter()
override this.CanConvert(t) = (t = typedefof<'a>)
override this.CanRead = false
override this.CanWrite = false
override this.WriteJson(_, _, _) = raise (NotImplementedException());
override this.ReadJson(_, _, _, _) = raise (NotImplementedException());
Then apply it to your type as follows (demo fiddle #1 here):
type [<JsonConverterAttribute(typeof<NoConverter<C>>); System.ComponentModel.TypeConverterAttribute(typeof<CC>)>] C = A of string
and CC() =
inherit System.ComponentModel.TypeConverter()
override this.CanConvertFrom (_, t) = (t = typeof<string>)
override this.ConvertFrom(_, _, s) = s :?> string |> A |> box<C>
override this.CanConvertTo (_, t) = t = typeof<string>
override this.ConvertTo(_, _, s, _) = s :?> C |> fun (A s) -> s |> box<string>
printfn "%s" (Newtonsoft.Json.JsonConvert.SerializeObject(A "123"))
Or, use it in settings as follows (demo fiddle #2 here):
let settings = JsonSerializerSettings(Converters = [|NoConverter<C>()|])
printfn "%s" (Newtonsoft.Json.JsonConvert.SerializeObject(A "123", settings))
Json.NET: Serializing a Charting Series just returns type-name
Thanks to @dbc pointing me to the NoTypeConverterJsonConverter<T>
discussed here I was able to solve my issue.
I made a modified version of the NoTypeConverterJsonConverter<T>
which instead takes a list of types to apply NoType Conversion:
public class NoTypeConverterJsonConverter : JsonConverter
{
private NoTypeConverterContractResolver Resolver = new NoTypeConverterContractResolver();
/// <summary>
/// The types where the default typeconverter will not be used
/// </summary>
public List<Type> TypesToOverride
{
get { return Resolver.DefaultedTypes; }
set { Resolver.DefaultedTypes = value; }
}
private class NoTypeConverterContractResolver : DefaultContractResolver
{
/// <summary>
/// The types where the default typeconverter will not be used
/// </summary>
public List<Type> DefaultedTypes = new List<Type>();
protected override JsonContract CreateContract(Type objectType)
{
// if its in the listed types
if (DefaultedTypes.Any(t => t.IsAssignableFrom(objectType)))
{
// create a default contract
JsonObjectContract contract = base.CreateObjectContract(objectType);
// Null out the converter to not use the default typeconverter.
contract.Converter = null;
return contract;
}
// if it decends from a list
else if (typeof(IList<>).IsAssignableFrom(objectType))
{
// create a contract from the object, avoiding any presets(?)
JsonObjectContract contract = this.CreateObjectContract(typeof(IList<>));
//contract.Converter = null; // Also null out the converter to prevent infinite recursion.
return contract;
}
try
{
// use default contract creator
return base.CreateContract(objectType);
}
catch (Exception ex)
{
// see if it can be treated as a list
if (typeof(IList<>).IsAssignableFrom(objectType))
{
// create a contract from the object, avoiding any presets(?)
JsonObjectContract contract = this.CreateObjectContract(typeof(IList<>));
//contract.Converter = null; // Also null out the converter to prevent infinite recursion.
return contract;
}
throw ex;
}
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
return property;
}
}
/// <summary>
/// Default Constructor
/// </summary>
/// <param name="skippedTypes"></param>
public NoTypeConverterJsonConverter(List<Type> skippedTypes = null)
{
if (skippedTypes != null)
{
TypesToOverride = skippedTypes;
}
}
public override bool CanConvert(Type objectType)
{
return TypesToOverride.Any(t => 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);
}
}
I also found that Collections like DataPointCollection
tended to be treated poorly, only returning the count, so I added the IList part:
// see if it can be treated as a list
if (typeof(IList<>).IsAssignableFrom(objectType))
{
// create a contract from the object, avoiding any presets(?)
JsonObjectContract contract = this.CreateObjectContract(typeof(IList<>));
//contract.Converter = null; // Also null out the converter to prevent infinite recursion.
return contract;
}
The code is then used like so:
// The types to not use the default typeconverter
List<Type> skippedTypes = new List<Type>()
{
typeof(DataPoint),
typeof(DataPointCustomProperties),
typeof(SmartLabelStyle)
};
// create converter
NoTypeConverterJsonConverter converter = new NoTypeConverterJsonConverter(skippedTypes);
// use the converter to serialize a series
string seriesstr = JsonConvert.SerializeObject(series, Formatting.Indented,
new JsonSerializerSettings()
{
Converters = new List<JsonConverter> { converter }
});
// deserialise using the same converter
Series series2 = JsonConvert.DeserializeObject<Series>(seriesstr, new JsonSerializerSettings()
{
Converters = new List<JsonConverter> { converter }
});
Effectively, just add any type that's giving you trouble to that list and it usually sorts it out.
Related Topics
Using the Iterator Variable of Foreach Loop in a Lambda Expression - Why Fails
Differencebetween Declarative and Imperative Paradigm in Programming
C# Short/Long/Int Literal Format
How to Create a Wpf Usercontrol with Named Content
Generate a PDF That Automatically Prints
Restsharp JSON Parameter Posting
Why Should I Use Int Instead of a Byte or Short in C#
How to Set the Timeout for a Tcpclient
Hyphenated HTML Attributes with ASP.NET MVC
Simple State MAChine Example in C#
How to Get the Last Day of a Month
How to Implement Real Time Data for a Web Page