Serialize struct/class as a single string - c#

I have a struct that I use in my ViewModels in order to simplify the way they get automatically serialized. I want to the serialization of it to simply call the .ToString() method. Currently when it's serialized to JSON it gets turned into: "{}". Here is my Date class:
public struct Date
{
private DateTime _date;
public Date(DateTime date)
{
_date = date;
}
public static implicit operator Date(DateTime date) => new Date(date);
public override string ToString() => _date.ToString("yyyy-MM-dd");
}
I thought that there might be some sort of attribute that I can decorate the struct with or perhaps some interface that I can implement but it doesn't appear to help.

A Lot of times, this is required in a lot of Classes when the project is bigger.
We need the conversion to work both ways. A class should be serialized using the ToString() method, and the class should be deserialized from the String. We use the following convention.
Define marker interfaces to let classes explicitly adhere to the contract that they support Serialization using the ToString method and also support Deserialization from string to object instance.
/// <summary>
/// Interface to convert string to a type T
/// </summary>
public interface IStringToTypeConverter<out T>
{
T ConvertToType(string stringToConvertFrom);
}
/// <summary>
/// Marker Interface to let Serialization/Deserialization work on the ToString Method of the class, Rather than
/// calling on the Instance properties
/// </summary>
public interface ITypeToStringConverter
{
}
Next, Define the generic converter which will do the conversion (Serialization/Deserialization) for a class which implements the above interfaces.
public class ToStringJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var isTypeImplementStringToTypeConverter = objectType.GetInterfaces().Any(x =>
x == typeof(ITypeToStringConverter) ||
(x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IStringToTypeConverter<>)));
return isTypeImplementStringToTypeConverter;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load the JSON for the Result into a JObject
var stringValue = reader.Value.ToString();
if (string.IsNullOrWhiteSpace(stringValue))
{
var jObject = JObject.Load(reader);
stringValue = jObject.ToString();
}
MethodInfo parse = objectType.GetMethod("ConvertToType");
if (parse != null)
{
var destinationObject = Activator.CreateInstance(objectType,stringValue);
return parse.Invoke(destinationObject, new object[] { stringValue });
}
throw new JsonException($"The {objectType.Name} type does not have a public ConvertToType(string) method.");
}
}
Lastly, Add the Converter to the startup class, passing it in the JSON Options
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new ToStringJsonConverter());
})
Note: Please test the performance benhmarks for your code to see if it has any impact on your performance SLA.

After some further research it looks like MVC uses the JsonConverter attribute to serialize JsonResults. The code below accomplished what I was trying to do.
[JsonConverter(typeof(DateToString))]
public struct Date
{
private DateTime _date;
public Date(DateTime date)
{
_date = date;
}
public static implicit operator Date(DateTime date) => new Date(date);
public override string ToString() => _date.ToString("yyyy-MM-dd");
}
public class DateToString : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(Date);
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> throw new NotImplementedException();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
writer.WriteValue(value.ToString());
}

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

Json.NET - Serialize generic type wrapper without property name

I have a generic type that wraps a single primitive type to give it value equality semantics
public class ValueObject<T>
{
public T Value { get; }
public ValueObject(T value) => Value = value;
// various other equality members etc...
}
It is used like:
public class CustomerId : ValueObject<Guid>
{
public CustomerId(Guid value) : base(value) { }
}
public class EmailAddress : ValueObject<string>
{
public EmailAddress(string value) : base(value) { }
}
The issue is when serializing a type like:
public class Customer
{
public CustomerId Id { get; }
public EmailAddress Email { get; }
public Customer(CustomerId id, EmailAddress email)
{
Id = id;
Email = email;
}
}
Each object the inherits from ValueObject<T> is wrapped in a Value property (as expected). For example
var customerId = new CustomerId(Guid.NewGuid());
var emailAddress = new EmailAddress("some#email.com");
var customer = new Customer(customerId, emailAddress);
var customerAsJson = JsonConvert.SerializeObject(customer, Formatting.Indented, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})
Results in
{
"id": {
"value": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c"
},
"email": {
"value": "some#email.com"
}
}
Is there a way to write a custom JsonConverter so the the Value property is excluded for types subclassing ValueObject<T> so that the above example would output
{
"id": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c",
"email": "some#email.com"
}
I would prefer to have a single JsonConverter that can handle all ValueObject<T> rather than having to define a separate JsonConverter for each ValueObject<T> subclass
My first attempt was
public class ValueObjectOfTConverter : JsonConverter
{
private static readonly Type ValueObjectGenericType = typeof(ValueObject<>);
private static readonly string ValuePropertyName = nameof(ValueObject<object>.Value);
public override bool CanConvert(Type objectType) =>
IsSubclassOfGenericType(objectType, ValueObjectGenericType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// converts "f5ce21a5-a0d1-4888-8d22-6f484794ac7c" => "value": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c"
var existingJsonWrappedInValueProperty = new JObject(new JProperty(ValuePropertyName, JToken.Load(reader)));
return existingJsonWrappedInValueProperty.ToObject(objectType, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// to implement
}
private static bool IsSubclassOfGenericType(Type typeToCheck, Type openGenericType)
{
while (typeToCheck != null && typeToCheck != typeof(object))
{
var cur = typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck;
if (openGenericType == cur) return true;
typeToCheck = typeToCheck.BaseType;
}
return false;
}
}
You can do this with a custom JsonConverter similar to the ones shown in Json.Net: Serialize/Deserialize property as a value, not as an object. However, since ValueObject<T> does not have a non-generic method to get and set the Value as an object, you will need to use reflection.
Here's one approach:
class ValueConverter : JsonConverter
{
static Type GetValueType(Type objectType)
{
return objectType
.BaseTypesAndSelf()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueObject<>))
.Select(t => t.GetGenericArguments()[0])
.FirstOrDefault();
}
public override bool CanConvert(Type objectType)
{
return GetValueType(objectType) != null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// You need to decide whether a null JSON token results in a null ValueObject<T> or
// an allocated ValueObject<T> with a null Value.
if (reader.SkipComments().TokenType == JsonToken.Null)
return null;
var valueType = GetValueType(objectType);
var value = serializer.Deserialize(reader, valueType);
// Here we assume that every subclass of ValueObject<T> has a constructor with a single argument, of type T.
return Activator.CreateInstance(objectType, value);
}
const string ValuePropertyName = nameof(ValueObject<object>.Value);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var valueProperty = contract.Properties.Where(p => p.UnderlyingName == ValuePropertyName).Single();
// You can simplify this to .Single() if ValueObject<T> has no other properties:
// var valueProperty = contract.Properties.Single();
serializer.Serialize(writer, valueProperty.ValueProvider.GetValue(value));
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
}
You could then apply the converter directly to ValueType<T> like so:
[JsonConverter(typeof(ValueConverter))]
public class ValueObject<T>
{
// Remainder unchanged
}
Or apply it in settings instead:
var settings = new JsonSerializerSettings
{
Converters = { new ValueConverter() },
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var customerAsJson = JsonConvert.SerializeObject(customer, Formatting.Indented, settings);
Working sample .Net fiddle #1 here.
Alternatively, you might consider adding a non-generic method to access the value as an object, e.g. like so:
public interface IHasValue
{
object GetValue(); // A method rather than a property to ensure the non-generic value is never serialized directly.
}
public class ValueObject<T> : IHasValue
{
public T Value { get; }
public ValueObject(T value) => Value = value;
// various other equality members etc...
#region IHasValue Members
object IHasValue.GetValue() => Value;
#endregion
}
With this addition, WriteJson() becomes much simpler:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, ((IHasValue)value).GetValue());
}
Working sample .Net fiddle #2 here.
Notes:
ReadJson() assumes that every subclass of Value<T> has a public constructor taking a single argument of type T.
Applying the converter directly to ValueType<T> using [JsonConverter(typeof(ValueConverter))] will have slightly better performance, since CanConvert need never get called. See Performance Tips: JsonConverters for details.
You need to decide how to handle a null JSON token. Should it result in a null ValueType<T>, or an allocated ValueType<T> with a null Value?
In the second version of ValueType<T> I implemented IHasValue.GetValue() explicitly to discourage its use in cases where an instance of ValueType<T> is used in statically typed code.
If you really only want to apply the converter to types subclassing ValueObject<T> and not ValueObject<T> itself, in GetValueType(Type objectType) add a call to .Skip(1):
static Type GetValueType(Type objectType)
{
return objectType
.BaseTypesAndSelf()
.Skip(1) // Do not apply the converter to ValueObject<T> when not subclassed
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueObject<>))
.Select(t => t.GetGenericArguments()[0])
.FirstOrDefault();
}
And then apply the converter in JsonSerializerSettings.Converters rather than directly to ValueObject<T>.

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

Deserialization of JSON from previous data model with Custom Converters fails when deserializing collections of Interfaces

I am responsible for maintaining a game system where users persist JSON serialized POCOs in a local cache to preserve state of, e.g., a Character.
The newest version of the code has changed the data model of these serialized objects. In particular, a new interface was created. This is creating issues when deserializing old copies of characters into the new code. I am attempting to resolve these with custom converters, but I'm running into trouble.
old, serialized version:
public class Character{
public Skill Parent {get;set;}
public Dictionary<string,Skill} Skills {get;set;}
}
public class Skill {
//normal stuff here.
}
new version:
public class Character{
[JsonProperty, JsonConverter(typeof(ConcreteTypeConverter<Dictionary<string,Skill>>))]
public Dictionary<string,ISkill} Skills {get;set;}
}
public class Skill:ISkill {
//normal stuff here.
}
public interface ISkill{
//stuff that all skill-like things have here
}
I have further defined a custom converter class (having read this and this,
but i'm still running into trouble deserializing collections.
public class Extensions
{
//a lot of serializer extensions here including the Deserialize method
private static readonly CustomSerializationBinder Binder = new CustomSerializationBinder();
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.Objects,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
Binder = Binder,
};
}
public class CustomSerializationBinder : DefaultSerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
return base.BindToType(assemblyName, typeName);
}
}
public class ConcreteTypeConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer,value); // serialization isnt't the problem.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (typeof (T)==typeof(Dictionary<string,Skill>))
{
var retVal = new object();
if (reader.TokenType == JsonToken.StartObject)
{
T instance = (T)serializer.Deserialize(reader, typeof(T)); //crashes here
retVal = new List<T>() { instance };
return retVal;
}
}
return serializer.Deserialize<T>(reader);
}
public override bool CanConvert(Type objectType)
{
return true; // kind of a hack
}
}
So i have an old Dictionary<string,Skill> and I can't cast that to Dictionary<string,ISkill> in any code-path that I can see. How should I resolve this?
Since your legacy JSON already contains type information for all objects including dictionary objects, what you need to do is to strip the type information for dictionaries and allow the deserialized dictionary type to be controlled by the code, not the JSON.
The following converter should do the job:
public class IgnoreDictionaryTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.GetDictionaryKeyValueTypes().Count() == 1;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
var obj = JObject.Load(reader);
obj.Remove("$type");
using (var subReader = obj.CreateReader())
{
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class TypeExtensions
{
/// <summary>
/// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
{
if (type == null)
throw new ArgumentNullException();
if (type.IsInterface)
return new[] { type }.Concat(type.GetInterfaces());
else
return type.GetInterfaces();
}
public static IEnumerable<Type[]> GetDictionaryKeyValueTypes(this Type type)
{
foreach (Type intType in type.GetInterfacesAndSelf())
{
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
yield return intType.GetGenericArguments();
}
}
}
}
Then you could add it in settings, or apply it to the dictionary property in question:
public class Character
{
[JsonConverter(typeof(IgnoreDictionaryTypeConverter))]
public IDictionary<string, ISkill> Skills { get; set; }
}
For the future, you might also want disable emitting of type information for dictionaries, since dictionaries are collections, and collection types are better specified by the code, not the JSON:
public class Character
{
[JsonConverter(typeof(IgnoreDictionaryTypeConverter))]
[JsonProperty(TypeNameHandling = TypeNameHandling.None)]
public IDictionary<string, ISkill> Skills { get; set; }
}

Custom inheritance JsonConverter fails when JsonConverterAttribute is used

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.

Categories