Custom inheritance JsonConverter fails when JsonConverterAttribute is used - c#

I am trying to deserialize derived type, and I want to use a custom property Type to distinguish between derived types.
[
{
"Type": "a",
"Height": 100
},
{
"Type": "b",
"Name": "Joe"
}
]
The solution I came to was to create a custom JsonConverter. On ReadJson I read the Type property and instantiate that type through the ToObject<T> function. Everything works fine until I use a JsonConverterAttribute. The ReadJson method loops infinitely because the attribute is applied on subtypes too.
How do I prevent this attribute from being applied to the subtypes?
[JsonConverter(typeof(TypeSerializer))]
public abstract class Base
{
private readonly string type;
public Base(string type)
{
this.type = type;
}
public string Type { get { return type; } }
}
public class AType : Base
{
private readonly int height;
public AType(int height)
: base("a")
{
this.height = height;
}
public int Height { get { return height; } }
}
public class BType : Base
{
private readonly string name;
public BType(string name)
: base("b")
{
this.name = name;
}
public string Name { get { return name; } }
}
public class TypeSerializer : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Base);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var j = JObject.Load(reader);
var type = j["Type"].ToObject<string>();
if (type == "a")
// Infinite Loop! StackOverflowException
return j.ToObject<AType>();
if (type == "b")
return j.ToObject<BType>();
throw new NotImplementedException(type);
}
}
[TestFixture]
public class InheritanceSerializeTests
{
[Test]
public void Deserialize()
{
var json = #"{""Type"":""a"", ""Height"":100}";
JObject.Parse(json).ToObject<Base>(); // Crash
}
}

I had a very similar problem with a project that I am currently working on: I wanted to make a custom JsonConverter and map it to my entities via attributes, but then the code got trapped in an infinite loop.
What did the trick in my case was using serializer.Populate instead of JObject.ToObject (I couldn't use .ToObject even if I wanted to; I am using version 3.5.8, in which this function does not exist). Below is my ReadJson method as an example:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JContainer lJContainer = default(JContainer);
if (reader.TokenType == JsonToken.StartObject)
{
lJContainer = JObject.Load(reader);
existingValue = Convert.ChangeType(existingValue, objectType);
existingValue = Activator.CreateInstance(objectType);
serializer.Populate(lJContainer.CreateReader(), existingValue);
}
return existingValue;
}

Remove the [JsonConverter(typeof(TypeSerializer))] attribute from the Base class and in the Deserialize test replace the following line:
JObject.Parse(json).ToObject<Base>(); // Crash
with this one:
var obj = JsonConvert.DeserializeObject<Base>(json, new TypeSerializer());
UPDATE 1 This update matches the comment from the asker of the question:
Leave the [JsonConverter(typeof(TypeSerializer))] attribute to the Base class. Use the following line for deserialization:
var obj = JsonConvert.DeserializeObject<Base>(json);
and modify the ReadJson method like this:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var j = JObject.Load(reader);
if (j["Type"].ToString() == "a")
return new AType(int.Parse(j["Height"].ToString()));
return new BType(j["Name"].ToString());
}

JsonConverters are inherited from base classes. There currently is no option to limit the JsonConverter to only a base class. You can overwrite it though.
Tested on Newtonsoft.Json 12.0.3
public class DisabledConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override bool CanRead => false;
public override bool CanWrite => false;
}
Then overwrite the JsonConverter on the derived classes.
[JsonConverter(typeof(DisabledConverter))]
public class AType : Base
...
[JsonConverter(typeof(DisabledConverter))]
public class BType : Base
...
Details
This only applies to the code:
if (type == "a")
return j.ToObject<AType>();
if (type == "b")
return j.ToObject<BType>();
When calling .ToObject it will try to use the converter (again) defined on the base class to deserialize to the object. This is what makes an infinite loop.
You need to override the JsonConverter on the derived class.
The CanRead => false and CanWrite => false will disable the custom JsonConverter for that class forcing the .ToObject call to use the default logic internally inside Newtonsoft.Json instead of your TypeSerializer class.

I had a similar problem to this and encountered the infinite loop.
The Api I was consuming could return an error response or the expected type.
I got around the problem in the same way as others have highlighted with using serializer.Populate.
public class Error{
public string error_code { get; set; }
public string message { get; set; }
}
[JsonConverter(typeof(CustomConverter<Success>))]
public class Success{
public Guid Id { get; set; }
}
public class CustomConverter<T> : JsonConverter where T : new() {
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
if (jObject.ContainsKey("error_code")) {
return jObject.ToObject(typeof(ProvisoErrorResponse));
}
var instance = new T();
serializer.Populate(jObject.CreateReader(), instance);
return instance;
}
}
Then used by HttpClient like this:
using (var response = await _httpClient.GetAsync(url))
{
return await response.Content.ReadAsAsync<Success>();
}
Why does this loop occur? I think we're assuming a fallacy.
I initially tried calling base.ReadJson thinking that I was overriding existing functionality when in fact there are many JsonConverters and our custom converter isnt overriding anything as the base class has no real methods. It would be better to treat the base class like an interface instead.
The loop occurs because the converter registered by us is the converter that the engine considers most applicable to the type to be converted. Unless we can remove our own converter from the converter list at runtime, calling on the engine to deserialize while within our custom converter will create infinite recursion.

Related

How to format a custom type in the json result?

I have a custom class, which basically boils down to this:
public class MyValue
{
public MyValue(int v)
{
Value = v;
}
public int Value {get;}
}
I use this class as a property on various classes. When my API returns a class (which has a MyValue property), the json returned looks like:
"propertyOfTypeMyValue": {
"value": 4
}
I don't want this. What I'd like is that the json returned looks like this:
"propertyOfTypeMyValue": 4
Is this possible? If so, how?
Yes, it is possible to get the output you want by creating a custom JsonConverter for your MyValue class like this:
public class MyValueConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MyValue);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType == JsonToken.Integer)
return new MyValue(Convert.ToInt32(reader.Value));
throw new JsonException("Unexpected token type: " + reader.TokenType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((MyValue)value).Value);
}
}
To use the converter, mark the MyValue class with a [JsonConverter] attribute:
[JsonConverter(typeof(MyValueConverter))]
public class MyValue
{
public MyValue(int v)
{
Value = v;
}
public int Value { get; private set; }
}
Here is a working demo: https://dotnetfiddle.net/A4eU87

How can I choose what type to deserialize at runtime based on the structure of the json?

I have some data stored as Json. One property in the data is either an integer (legacy data) like so:
"Difficulty": 2,
Or a complete object (new versions):
"Difficulty": {
"$id": "625",
"CombatModifier": 2,
"Name": "Normal",
"StartingFunds": {
"$id": "626",
"Value": 2000.0
},
"Dwarves": [
"Miner",
"Miner",
"Miner",
"Crafter"
]
},
I am trying to write a custom converter for the type that allows deserialization of both versions.
This is C#, using the latest version of newtonsoft.json.
I've written a converter, and deserializing the integer format is trivial - it's only the mix that is causing me trouble. The only way I can think to check is to try and fail; but this appears to leave the reader in an unrecoverable state. Also, calling deserialize in the catch block leads to an infinite loop.
public class DifficultyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
var jObject = serializer.Deserialize<JValue>(reader);
if (jObject.Value is Int32 intv)
return Library.EnumerateDifficulties().FirstOrDefault(d => d.CombatModifier == intv);
else
return null;
}
catch (Exception e)
{
return serializer.Deserialize<Difficulty>(reader);
}
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Difficulty);
}
}
Ideally I would be able to serialize into the new format always, and still support reading both formats. A couple of other options include:
Creating another serializer object that does not include the custom converter and calling it from the catch block.
Detecting out of date files at load and modifying the text before attempting to deserialize.
Kind of want to avoid those tho.
You have a couple of problems here:
You are getting an infinite recursion in calls to ReadJson() because your converter is registered with the serializer you are using to do the nested deserialization, either through settings or by directly applying [JsonConverter(typeof(DifficultyConverter))] to Difficulty.
The standard solution to avoid this is to manually allocate your Difficulty and then use serializer.Populate() to deserialize its members (e.g. as shown in this answer to Json.NET custom serialization with JsonConverter - how to get the "default" behavior) -- but you are also using PreserveReferencesHandling.Objects, which does not work with this approach.
What does work with reference preservation is to adopt the approach from this answer to JSON.Net throws StackOverflowException when using [JsonConvert()] and deserialize to some DTO that contains a property of type Difficulty which has a superseding converter applied directly to the property.
serializer.Deserialize<JValue>(reader); may advance the reader past the current token. This will cause the later attempt to deserialize as an object to fail.
Instead, just check the JsonReader.TokenType or preload into a JToken and check the Type.
Putting the above together, your converter should look like the following:
public class DifficultyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
switch (token.Type)
{
case JTokenType.Null:
return null;
case JTokenType.Integer:
{
var intv = (int)token;
return Library.EnumerateDifficulties().FirstOrDefault(d => d.CombatModifier == intv);
}
case JTokenType.Object:
return token.DefaultToObject(objectType, serializer);
default:
throw new JsonSerializationException(string.Format("Unknown token {0}", token.Type));
}
}
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(Difficulty);
}
Using the following extension methods:
public static partial class JsonExtensions
{
public static object DefaultToObject(this JToken token, Type type, JsonSerializer serializer = null)
{
var oldParent = token.Parent;
var dtoToken = new JObject(new JProperty(nameof(DefaultSerializationDTO<object>.Value), token));
var dtoType = typeof(DefaultSerializationDTO<>).MakeGenericType(type);
var dto = (IHasValue)(serializer ?? JsonSerializer.CreateDefault()).Deserialize(dtoToken.CreateReader(), dtoType);
if (oldParent == null)
token.RemoveFromLowestPossibleParent();
return dto == null ? null : dto.GetValue();
}
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var contained = node.Parent is JProperty ? node.Parent : node;
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (contained is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
interface IHasValue
{
object GetValue();
}
[JsonObject(NamingStrategyType = typeof(Newtonsoft.Json.Serialization.DefaultNamingStrategy), IsReference = false)]
class DefaultSerializationDTO<T> : IHasValue
{
public DefaultSerializationDTO(T value) { this.Value = value; }
public DefaultSerializationDTO() { }
[JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)]
public T Value { get; set; }
public object GetValue() => Value;
}
}
public class NoConverter : JsonConverter
{
// NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
// To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
// By https://stackoverflow.com/users/3744182/dbc
public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); }
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
}
Demo fiddle here.

How to ignore base class JsonConverter when deserializing derived classes?

I have an abstract base class:
[JsonConverter(typeof(Converter))]
public abstract class TextComponent {
...
public bool Bold { get; set; }
public TextComponent[] Extra { get; set; }
...
}
And more classes which inherits from it. One of those classes is StringComponent:
public sealed class StringComponent : TextComponent
{
public string Text { get; set; }
public StringComponent(string text)
{
Text = text;
}
}
Converter, which is a JsonConverter applied to TextComponent looks like this:
private sealed class Converter : JsonConverter
{
....
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var tok = JToken.Load(reader);
switch (tok)
{
...
case JObject x:
var dic = (IDictionary<string, JToken>) x;
if (dic.ContainsKey("text")) return x.ToObject<StringComponent>();
...
...
}
}
...
public override bool CanConvert(Type objectType) => objectType == typeof(TextComponent);
}
The problem:
var str = "{\"text\":\"hello world\"}";
var obj = JsonConvert.DeserializeObject<TextComponent>(str);
// this doesn't work either:
var obj = JsonConvert.DeserializeObject<StringComponent>(str);
This goes into an infinite "loop" eventually resulting in a StackOverflow, because when calling DeserializeObject<Stringcomponent> or ToObject<StringComponent>, the JsonConverter of the base class (the Converter) is used which again calls those methods. This is not the desired behavior. When serializing derived classes, they should not be using base class's JsonConverter. If you look at CanConvert method of the Converter, I'm also only allowing it for TextComponent only, not for any of it's derived classes.
So how do I fix this?
You can set the converter on the sub class contract to null;
public override bool CanWrite => false;
public override bool CanRead => true;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(BaseClass))
{
JObject item = JObject.Load(reader);
if (item["isClass2"].Value<bool>())
{
return item.ToObject<ChildClass2>(serializer);
}
else
{
return item.ToObject<ChildClass1>(serializer);
}
}
else
{
serializer.ContractResolver.ResolveContract(objectType).Converter = null;
return serializer.Deserialize(reader, objectType);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Did you try to remove the [JsonConvert]-attribute from your base class? In my example I invoke my "custom" converter manually:
https://github.com/Code-Inside/Samples/blob/b635580a4966b1b77c93a8b682389c6cf06d2da6/2015/JsonConvertIssuesWithBaseClasses/JsonConvertIssuesWithBaseClasses/Program.cs#L36-L79

How to call JsonConvert.DeserializeObject and disable a JsonConverter applied to a base type via [JsonConverter]?

EDIT: Clarify question:
I have overridden the JsonConverter for a base type (by applying [JsonConverter(typeof(TConverter))] to the superclass), but when deserializing the sub-type directly I want to use STANDARD serialization (i.e. no custom converter) for deserializing my derived object. How do I specify STANDARD serialization for use in the deserialize method, as if I had NOT overridden the JsonConverter?
I am using elastic search and can't call JsonConvert.DeserializeObject with my custom implementation of JsonConverter, and have to rely on the attribute for Elastic to use my converter.
However, using this converter as attribute seems to affect all sub classes as well, but I just want them to use the standard converter, so that I don't have to implement JsonConverter for each of many implementations.
This is my classes/logic as I would like it to look:
[Route("test")]
[HttpPost]
public HttpResponseMessage Test([FromBody] JToken json)
{
var res = json.ToObject<Product>(); // I want an object of ProductImpl type here
return Request.CreateResponse(res);
}
[JsonConverter(typeof(JsonProductConverted))]
public abstract class Product
{
}
public class ProductImpl : Product
{
}
public class JsonProductConverted : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject json = JObject.Load(reader);
//var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in
var type = typeof(ProductImpl);
// var res = JsonConvert.DeserializeObject(json.ToString(), type, DEFAULT_JSONCONVERTER_HERE);
var res = DeserializeToObjectWithStandardJsonConverter(json, type);
return res;
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
If I don't supply the default JsonConverter, or similar it will just use the JsonProductConverted converter, which creates an infinite loop.
Since you have added [JsonConverter(typeof(JsonProductConverted))] directly to your Product type, you could add a dummy converter to ProductImpl that returns false from CanRead and CanWrite:
[JsonConverter(typeof(NoConverter))]
public class ProductImpl : Product
{
}
public class NoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return false;
}
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This overrides the base class's converter and then falls back on default serialization for both reading and writing
Sample .Net fiddle.
Another option would be to use serializer.Populate(). This avoids the call to the converter for the object itself:
public class JsonProductConverted : JsonTypeInferringConverterBase
{
protected override Type InferType(Type objectType, JObject json)
{
//var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in
return typeof(ProductImpl);
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
public abstract class JsonTypeInferringConverterBase : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
protected abstract Type InferType(Type objectType, JObject json);
protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(actualType);
return contract.DefaultCreator();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var json = JObject.Load(reader);
var actualType = InferType(objectType, json);
// Construct object (or reuse existingValue if compatible)
if (existingValue == null || !actualType.IsAssignableFrom(existingValue.GetType()))
{
existingValue = CreateObject(actualType, serializer, json);
}
// Populate object.
using (var subReader = json.CreateReader())
{
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
}
Note that the concrete objects must have parameterless constructors for this to work. If not, you can override protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json) and manually invoke a parameterized constructor by deserializing select properties inside the JObject json.
Sample fiddle #2.

Object cannot by deserialized, because 'Collection was of a fixed size.'

I have easy example of my real code. I need serialize to JSON and deserialize back object of class TestClass, which is derived from Letters. Both classes have constructor with parameter.
public class TestClass : Letters
{
public string[] Names { get; private set; }
public TestClass(string[] names)
: base(names)
// : base(new [] { "A", "B", })
// : base(names.Select(a => a.Substring(0, 1)).ToArray())
{
Names = names;
}
}
public abstract class Letters
{
public string[] FirstLetters { get; private set; }
protected Letters(string[] letters)
{
FirstLetters = letters;
}
}
Object of TestClass is serialized to valid JSON, but when I try it deserialize back to object, NotSupportedException is throw with message Collection was of a fixed size.
Here is my test
[Fact]
public void JsonNamesTest()
{
var expected = new TestClass(new [] { "Alex", "Peter", "John", });
var serialized = JsonConvert.SerializeObject(expected);
Console.WriteLine(serialized);
Assert.False(string.IsNullOrWhiteSpace(serialized));
var actual = JsonConvert.DeserializeObject<TestClass>(serialized);
AssertEx.PrimitivePropertiesEqual(expected, actual);
}
Json.Net needs all classes to have a parameterless constructor in order to deserialize them, otherwise it doesn't know how to call the constructor. One way to get around this without changing your classes is to make a custom JsonConverter that will create the object instance from the JSON. For example:
class TestClassConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(TestClass) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string[] names = jo["Names"].ToObject<string[]>();
return new TestClass(names);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Then, deserialize your class like this and it should work:
var actual = JsonConvert.DeserializeObject<TestClass>(serialized, new TestClassConverter());
Thank, it works! I modified your code for more general usage in my example.
I suppose
is only one public constructor
serialized parameters have same name as constructor parameters (ignore case)
public class ParametersContructorConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Letters).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
var contructor = objectType.GetConstructors().FirstOrDefault();
if (contructor == null)
{
return serializer.Deserialize(reader);
}
var parameters = contructor.GetParameters();
var values = parameters.Select(p => jo.GetValue(p.Name, StringComparison.InvariantCultureIgnoreCase).ToObject(p.ParameterType)).ToArray();
return contructor.Invoke(values);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}

Categories