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
Related
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
I am trying to create an abstraction layer for Json.NET deserialization using interfaces.
To achieve this I use custom JsonConverter which works just fine, until interfaces are introduced.
Following exception is thrown:
Unhandled Exception: Newtonsoft.Json.JsonSerializationException: Error
setting value to 'Items' on 'BatchList'. --->
System.InvalidCastException: Unable to cast object of type
'System.Collections.Generic.List1[BatchItems]' to type
'System.Collections.Generic.List`1[IBatchItems]
This is the setup to repro in a console app:
class Program
{
static void Main(string[] args)
{
var jsonBatch = #"{'items': [{'Id': 'name1','info': {'age': '20'}},{'Id': 'name2','info': {'age': '21'}}]}";
DeserializeAndPost(jsonBatch);
}
public static void DeserializeAndPost(string json)
{
IBatchList req;
req = JsonConvert.DeserializeObject<BatchList>(json);
Post(req);
}
public static void Post(IBatchList batchList)
{
Console.WriteLine(batchList.Items.FirstOrDefault().Id);
}
}
public interface IBatchList
{
List<IBatchItems> Items { get; set; }
}
public interface IBatchItems
{
string Id { get; set; }
JObject Info { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class BatchList : IBatchList
{
[JsonProperty(PropertyName = "Items", Required = Required.Always)]
[JsonConverter(typeof(SingleOrArrayConverter<BatchItems>))]
public List<IBatchItems> Items { get; set; }
}
[JsonObject]
public class BatchItems : IBatchItems
{
[JsonProperty(PropertyName = "Id", Required = Required.Always)]
public string Id { get; set; }
[JsonProperty(PropertyName = "Info", Required = Required.Always)]
public JObject Info { get; set; }
}
// JsonConverter
public class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<T> list = (List<T>)value;
if (list.Count == 1)
{
value = list[0];
}
serializer.Serialize(writer, value);
}
public override bool CanWrite
{
get { return true; }
}
}
I expect the output to be deserialized JSON as I provide the type for the interface to be used for deserialization:
[JsonConverter(typeof(SingleOrArrayConverter<BatchItems>))]
to be used.
Instead, unhandled cast exception is being thrown.
Note that if I use instead SingleOrArrayConverter<IBatchItems>, I will get an exception
Newtonsoft.Json.JsonSerializationException: Could not create an instance of type
as the [JsonConverter(typeof(SingleOrArrayConverter<BatchItems>))] is meant to provide concrete type for the following interface: public List<IBatchItems> Items { get; set; }.
What you need to do is to combine the functionality of the following two converters:
SingleOrArrayConverter from this answer to How to handle both a single item and an array for the same property using JSON.net by Brian Rogers.
This converter handles the frequently-encountered case where a one-item collection is not serialized as a collection; you are already using this converter.
ConcreteConverter<IInterface, TConcrete> from this answer to How to deserialize collection of interfaces when concrete classes contains other interfaces.
This converter deserializes a declared interface (here IBatchItems) into a specified concrete type (here BatchItems). This is required because IList<T> is not covariant and thus an IList<BatchItems> cannot be assigned to a IList<IBatchItems> as you are currently trying to do.
The best way to combine these two converters is to adopt the decorator pattern and enhance SingleOrArrayConverter to encapsulate a converter for each of the list's items inside the list converter:
public class SingleOrArrayListItemConverter<TItem> : JsonConverter
{
// Adapted from the answers to https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
// By Brian Rogers, dbc et. al.
readonly JsonConverter itemConverter;
readonly bool canWrite;
public SingleOrArrayListItemConverter(Type itemConverterType) : this(itemConverterType, true) { }
public SingleOrArrayListItemConverter(Type itemConverterType, bool canWrite)
{
this.itemConverter = (JsonConverter)Activator.CreateInstance(itemConverterType);
this.canWrite = canWrite;
}
public override bool CanConvert(Type objectType)
{
return typeof(List<TItem>).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.MoveToContent().TokenType == JsonToken.Null)
return null;
var contract = serializer.ContractResolver.ResolveContract(objectType);
var list = (ICollection<TItem>)(existingValue ?? contract.DefaultCreator());
if (reader.TokenType != JsonToken.StartArray)
{
list.Add(ReadItem(reader, serializer));
return list;
}
else
{
while (reader.ReadToContent())
{
switch (reader.TokenType)
{
case JsonToken.EndArray:
return list;
default:
list.Add(ReadItem(reader, serializer));
break;
}
}
// Should not come here.
throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
}
}
TItem ReadItem(JsonReader reader, JsonSerializer serializer)
{
if (itemConverter.CanRead)
return (TItem)itemConverter.ReadJson(reader, typeof(TItem), default(TItem), serializer);
else
return serializer.Deserialize<TItem>(reader);
}
public override bool CanWrite { get { return canWrite; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = value as ICollection<TItem>;
if (list == null)
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
if (list.Count == 1)
{
foreach (var item in list)
WriteItem(writer, item, serializer);
}
else
{
writer.WriteStartArray();
foreach (var item in list)
WriteItem(writer, item, serializer);
writer.WriteEndArray();
}
}
void WriteItem(JsonWriter writer, TItem value, JsonSerializer serializer)
{
if (itemConverter.CanWrite)
itemConverter.WriteJson(writer, value, serializer);
else
serializer.Serialize(writer, value);
}
}
public class ConcreteConverter<IInterface, TConcrete> : JsonConverter where TConcrete : IInterface
{
//Taken from the answer to https://stackoverflow.com/questions/47939878/how-to-deserialize-collection-of-interfaces-when-concrete-classes-contains-other
// by dbc
public override bool CanConvert(Type objectType)
{
return typeof(IInterface) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<TConcrete>(reader);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContent(this JsonReader reader)
{
if (reader.TokenType == JsonToken.None)
reader.Read();
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
public static bool ReadToContent(this JsonReader reader)
{
if (!reader.Read())
return false;
while (reader.TokenType == JsonToken.Comment)
if (!reader.Read())
return false;
return true;
}
}
Then apply it as follows:
[JsonObject(MemberSerialization.OptIn)]
public class BatchList : IBatchList
{
[JsonProperty(PropertyName = "Items", Required = Required.Always)]
[JsonConverter(typeof(SingleOrArrayListItemConverter<IBatchItems>), typeof(ConcreteConverter<IBatchItems, BatchItems>))]
public List<IBatchItems> Items { get; set; }
}
Notes:
This version of SingleOrArrayListItemConverter<TItem> avoids pre-loading the entire array into a JToken hierarchy which may improve performance.
If IBatchItems later becomes polymorphic, you could replace ConcreteConverter with a converter that intelligently selects the concrete type to use based on the properties present as shown in e.g. the answers to Deserializing polymorphic json classes without type information using json.net and How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?.
Demo fiddle here.
I have been able to do a custom Converter that transforms to a list of interfaces.
Here my custom converter:
public class InvoiceDetailConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
//assume we can convert to anything for now
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
List<InvoiceDetail> data = serializer.Deserialize<List<InvoiceDetail>>(reader);
// now loop to make the proper list
List<IInvoiceDetail> result = new List<IInvoiceDetail>();
foreach (IInvoiceDetail d in data) {
result.Add(d);
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
//use the default serialization - it works fine
serializer.Serialize(writer, value);
}
}
What I would like is to make this generic like
public class InvoiceDetailConverter <TConcrete, TInterface> : JsonConverter {
public override bool CanConvert(Type objectType) {
//assume we can convert to anything for now
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
List<TConcrete> data = serializer.Deserialize<List<TConcrete>>(reader);
// now loop to make the proper list
List<TInterface> result = new List<TInterface>();
foreach (TInterface d in data) {
result.Add(d);
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
//use the default serialization - it works fine
serializer.Serialize(writer, value);
}
}
At the End in my object it will be used like this:
public class test{
[JsonConverter(typeof(InvoiceDetailConverter<InvoiceDetail, IInvoiceDetail>))]
public List<IInvoiceDetail> InvoiceDetail { get; set; }
}
Is it possible to convert like this, as the above code won't compile. I'm using .NET Core.
I believe you're almost there, just specify dependency between TConcrete and TInterface:
public class InvoiceDetailConverter<TConcrete, TInterface> : JsonConverter
where TConcrete : TInterface // <------------------------------------------ add this
{
// ..........
}
Here you can find a demo of successful deserialization using the generic converter class for the following object:
public class test
{
[JsonConverter(typeof(InvoiceDetailConverter<InvoiceDetail, IInvoiceDetail>))]
public List<IInvoiceDetail> InvoiceDetail { get; set; }
[JsonConverter(typeof(InvoiceDetailConverter<VatDetail, IVatDetail>))]
public List<IVatDetail> VatDetail { get; set; }
}
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.
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);
}
}