Newtonsoft.JSON Cannot Convert Model with Typeconverter Attribute

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:

  1. As explained in @dbc's answer, I was using && instead of || in TimeConverter.CanConvertFrom().
  2. 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 default TypeConverter for Cid.

  • The JsonConverterAttribute(Type,Object[]) constructor is used to pass the specific TypeConverter type to the constructor of TypeConverterJsonConverter 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 to JsonProperty.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



Leave a reply



Submit