Deserialize a generic type - c#

I have a generic type Container<IContentType> where interface IContentType can be one of four concrete ContentX types.
I serialize and everything is fine.
When deserializing using Newtonsoft I use custom type converters and var model = JsonConvert.DeserializeObject<Container<ContentA>>(json, settings) works. The debugger shows I have an Container<ContentA> object.
My plan was when deserializing to attempt a deserialization for each of the four possible ContentX types and catch an exception silently until I "guess" the right one.
However, if I do this within a method like so:
public static Container<IContentType> Deserialize(jsonfile)
{
...
var model = JsonConvert.DeserializeObject<Container<ContentA>>(json, settings)
return model;
}
I get "Cannot implicitly convert Container<ContentA> to Container<IContentType>". ContentA implements IContentType.
Is there a way I can create a cast operator, conversion, dynamic or make the implicit conversion work?

Rather than trying to deserialize as a Container<ContentX> for concrete type(s) X, you should deserialize as a Container<IContentType> using a custom JsonConverter that pre-load the JSON into a JToken and infers the concrete type along the lines of How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects? or Deserializing polymorphic json classes without type information using json.net or Json.Net Serialization of Type with Polymorphic Child Object.
Thus your converter would look something like:
public class ContentConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IContentType);
}
Type GetConcreteType(JObject obj)
{
if (obj.GetValue(nameof(ContentA.SomePropertyOfContentA), StringComparison.OrdinalIgnoreCase) != null)
return typeof(ContentA);
// Add other tests for other content types.
// Return a default type or throw an exception if a unique type cannot be found.
throw new JsonSerializationException("Cannot determine concrete type for IContentType");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader);
var concreteType = GetConcreteType(obj);
return obj.ToObject(concreteType, serializer);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And your call to JsonConvert would look like:
var settings = new JsonSerializerSettings
{
Converters = { new ContentConverter() },
};
var model = JsonConvert.DeserializeObject<Container<IContentType>>(json, settings);
Finally, you might be able to choose the type entirely automatically using
new JsonDerivedTypeConverer<IContentType>(typeof(ContentA), typeof(ContentB), typeof(ContentC), typeof(ContentD))
Where JsonDerivedTypeConverer<T> is taken from JsonConverter with Interface.

Related

How to deserialize generic interface to generic concrete type with Json.Net?

I have below interface:
public interface IInterface<out M>
{
M Message { get; }
string Str { get; }
}
And its implementation:
public class Implementation<M> : IInterface<M>
{
public M Message;
public string Str;
public Implementation(M message, string str)
{
Message = message;
Str = str;
}
M IInterface<M>.Message => this.Message;
string IInterface<M>.Str => this.Str;
}
Here is a sample M class:
public class Sample
{
public int X;
}
Here is the sample JSON I pass from javascript client:
{ "Message" : { "X": 100 }, "Str" : "abc" }
Now there is some legacy/external code (that I can't change) which tries to deserialize the above JSON object using Json.Net using DeserializeObject<IInterface<Sample>>(js_object_string).
How can I write a JsonConverter for this IInterface interface that deals with its generic parameter M. Most of the solutions on internet only work with the types that are known at compile time.
I tried below code (that I don't understand fully) but the external code doesn't think the deserialized object is IInterface.
static class ReflectionHelper
{
public static IInterface<T> Get<T>()
{
var x = JsonConvert.DeserializeObject<T>(str);
IInterface<T> y = new Implementation<T>(x, "xyz");
return y;
}
}
class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IInterface<>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var w = Newtonsoft.Json.Linq.JObject.Load(reader);
var x = typeof(ReflectionHelper).GetMethod(nameof(ReflectionHelper.Get)).MakeGenericMethod(objectType.GetGenericArguments()[0]).Invoke(null, new object[] { });
return x;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // otherwise I get a circular dependency error.
serializer.Serialize(writer, value);
}
}
Your MyConverter can be written as follows:
public class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType) =>
objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(IInterface<>);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (!CanConvert(objectType)) // For safety.
throw new ArgumentException(string.Format("Invalid type {0}", objectType));
var concreteType = typeof(Implementation<>).MakeGenericType(objectType.GetGenericArguments());
return serializer.Deserialize(reader, concreteType);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
Then add it to Converters for serialization and deserialization as follows:
var settings = new JsonSerializerSettings
{
Converters = { new MyConverter() },
};
var root = JsonConvert.DeserializeObject<IInterface<Sample>>(js_object_string, settings);
And if you really cannot change the call to DeserializeObject<IInterface<Sample>>(js_object_string) at all, you can add your converter to Json.NET's global default settings for the current thread like so:
// Set up Json.NET's global default settings to include MyConverter
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = { new MyConverter() },
};
// And then later, deserialize to IInterface<Sample> via a call that cannot be changed AT ALL:
var root = JsonConvert.DeserializeObject<IInterface<Sample>>(js_object_string);
Alternatively, you could apply MyConverter directly to IInterface<out M> like so:
[JsonConverter(typeof(MyConverter))]
public interface IInterface<out M>
{
But if you do, you must apply NoConverter from this answer to How to deserialize generic interface to generic concrete type with Json.Net? to Implementation<M> to avoid a stack overflow exception:
[JsonConverter(typeof(NoConverter))]
public class Implementation<M> : IInterface<M>
{
Notes:
By overriding JsonConverter.CanWrite and returning false we avoid the need to implement WriteJson().
In ReadJson() we determine the concrete type to deserialize by extracting the generic parameters from the incoming objectType, which is required to be IInterface<M> for some M, and constructing a concrete type Implementation<M> using the same generic parameters.
Json.NET supports deserialization from a parameterized constructor as described in JSON.net: how to deserialize without using the default constructor?. Since your Implementation<M> has a single parameterized constructor that meets the requirements described, it is invoked to deserialize your concrete class correctly.
DefaultSettings applies to all calls to JsonConvert throughout your application, from all threads, so you should determine whether modifying these settings is appropriate for your application.
NoConverter must be applied to Implementation<M> because, in the absence of a converter of its own, it will inherit MyConverter from IInterface<out M> which will subsequently cause a recursive call to MyConverter.ReadJson() when deserializing the concrete type, resulting in a stack overflow or circular reference exception. (You can debug this yourself to confirm.)
For other options to generate a "default" deserialization of the concrete class without using a converter, see JSON.Net throws StackOverflowException when using [JsonConvert()] or Call default JsonSerializer in a JsonConverter for certain value type arrays. Answers that suggest constructing a default instance of the concrete type and then populating it using JsonSerializer.Populate() will not work for you because your concrete type does not have a default constructor.
Demo fiddles for DefaultSettings here, and for MyConverter + NoConverter here.

Ignore c# fields dynamically from Json Serialize

For API purposes I need to ignore some fields based on criteria I receive. Usually I could use [ScriptIgnore] attribute to do this.
But how I can ignore fields dynamically (based on some conditionals)?
Use JsonIgnore attribute available in Newtonsoft.Json package.
then, if you want it to be dynamically conditional, see ShouldSerialize
Assuming you use Json.Net, you can create your own converter for a specific type by creating a class that inherits from JsonConverter.
public class MyJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MyType);
}
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)
{
var objectToSerialize = new {}; //create the object you want to serialize here, based on your dynamic conditions
new JsonSerializer().Serialize(writer, objectToSerialize); //serialize the object to the current writer
}
}
Then you call JsonConvert.DeserializeObject and pass it your custom converter:
JsonConvert.DeserializeObject<MyType>(jsonString, new MyJsonConverter());

Can I parse json either into a string or another concrete type as object?

I'd like to have property of type object that can be either a string or Template type.
Is it possible to tell Json.NET to parse something into one of several specified types?
class MyClass
{
public object Template { get; set; }
}
where Template = "TemplateName"
{
"Template": "TemplateName"
}
or Template = new Template()
{
"Template": { "Name": "OtherTamplate", ... }
}
UPDATE:
I tried to follow #krillgar' advice and create a custom JsonConverter but unfortunatelly the CanConvert method receives only the target type, in this case object. This information is not enough to tell wheter it can be deserialized (in case I had other object properties). I guess I need it to be a Template after all or create a derived type like TemplateReference or something:
class myconverter : 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)
{
// objectType = typeof(object)
throw new NotImplementedException();
}
}
Configuration = JsonConvert.DeserializeObject<MyClass>(text, new myconverter());
Disclaimer
This question has once been closed as a duplicate of How to deserialize a JSON property that can be two different data types using Json.NET. Because at the time of writing my question I hadn't known that there already was a similar one I'd like to clarify the difference between them to prevent it from being closed in future:
The other question is about how to deserialize different values into a concrete type whereas mine is about deserializing different values into an object. It might seem to be the same at the first look because in both examples only the type of the property is different but it has a huge impact on the overall application design. It's important for me that I can use an object to store different specialized types rather then one type having multiple responsibilities.
This problem can be solved by using a custom JsonConverter. Here is a generic version that should work for this situation:
class ObjectOrStringConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when the [JsonConverter] attribute is used
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Object)
{
return token.ToObject<T>(serializer);
}
return token.ToString();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
To use the converter, all you need to do is add a [JsonConverter] attribute to the property in your class that can be either a string or an object. The generic type parameter must match the type of non-string object you are expecting.
class MyClass
{
[JsonConverter(typeof(ObjectOrStringConverter<Template>))]
public object Template { get; set; }
}
Below is a demonstration of the converter in action:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("--- first run ---");
string json = #"
{
""Template"": ""TemplateName""
}";
DeserializeAndDump(json);
Console.WriteLine("--- second run ---");
json = #"
{
""Template"": { ""Name"": ""OtherTemplate"" }
}";
DeserializeAndDump(json);
}
static void DeserializeAndDump(string json)
{
MyClass obj = JsonConvert.DeserializeObject<MyClass>(json);
if (obj.Template == null)
{
Console.WriteLine("Template property is null");
}
else
{
Console.WriteLine("Template property is a " + obj.Template.GetType().Name);
string name = "(unknown)";
if (obj.Template is Template) name = ((Template)obj.Template).Name;
else if (obj.Template is string) name = (string)obj.Template;
Console.WriteLine("Template name is \"" + name + "\"");
}
Console.WriteLine();
}
}
class Template
{
public string Name { get; set; }
}
And here is the output from the above:
--- first run ---
Template property is a String
Template name is "TemplateName"
--- second run ---
Template property is a Template
Template name is "OtherTemplate"
Fiddle: https://dotnetfiddle.net/Lw3RaN
I don't know if you can do that, but you could go another way. Change your "Template" property to be a Template instead of an object and use a custom property of the Template class to know weither you want to serialize it as a Template or a string.
class MyClass
{
[JsonConverter(typeof(TemplateConverter))]
public Template Template { get; set; }
}
class Template
{
/* Your Template class */
public Type TypeToSerializeInto { get; private set; }
}
public class TemplateConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Template);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Template val = value as Template;
writer.WriteStartObject();
writer.WritePropertyName("Template");
if (val.TypeToSerializeInto == typeof(Template))
serializer.Serialize(writer, val);
else if (val.TypeToSerializeInto == typeof(string))
serializer.Serialize(writer, val.Name);
writer.WriteEndObject();
}
}
If you have a property, which type is an abstract type - like object, on de-serialization, you can know the specific type that was serialized by serializing somewhere also the name of that specific type.
So your json should look like this:
{
"MyClass": {
"Template": "some name",
"type": "System.String"
}
}
This way on deserialization you can check what type was that property before the serialization (in this case String)
Another way to determine the type is by checking the json structure as you can see here:
C#: Deserializing JSON when one field can be different types

Creating a custom JsonConverter to handle System.Text.Encoding objects

I have written a custom JsonConverter which I am hoping will allow me to serialize and deserialize Encoding objects within my classes:
public class EncodingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsSubclassOf(typeof(Encoding));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((Encoding)value).EncodingName);
}
public override bool CanRead { get { return true; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var name = reader.ReadAsString();
return Encoding.GetEncoding(name);
}
}
However, when I run the following test code, I get an exception when calling DeserializeObject, and the ReadJson method never gets called.
class Program
{
private static void Main(string[] args)
{
var test = new TestClass();
var jsonSettings = new JsonSerializerSettings
{
Converters = new[] { new EncodingConverter(), }
};
var json = JsonConvert.SerializeObject(test, jsonSettings);
var test2 = JsonConvert.DeserializeObject<TestClass>(json, jsonSettings);
}
}
class TestClass
{
public string Property1;
public Encoding Encoding = Encoding.UTF8;
}
The exception message is:
Target type System.Text.Encoding is not a value type or a non-abstract class.
Am I missing something?
There are three problems with your converter that I see.
You are using the wrong check in CanConvert().
You are using the wrong name for the Encoding when serializing.
You are using the wrong method to get the value from the reader when deserializing.
Let's take these one at a time.
First, in your CanConvert method you are using objectType.IsSubclassOf(typeof(Encoding)) to determine whether the converter should handle the Encoding. This works fine on serialization because you have a concrete instance of the encoding (e.g. UTF8Encoding), which is indeed a subclass of Encoding. However, on deserialization, the deserializer doesn't know what concrete type of encoding you are going to make, so the type that is passed to the converter is just Encoding. Since Encoding is not a subclass of itself, CanConvert returns false, and your ReadJson method never gets called. That leaves Json.Net to try to instantiate the Encoding itself, which it can't do (because Encoding is abstract), so it throws the error you mentioned in your question. You should instead use typeof(Encoding).IsAssignableFrom(objectType) inside your CanConvert method.
Second, when serializing the Encoding inside WriteJson, you are outputting the EncodingName property, which is the human-readable display name of the encoding, not the code page name. If you look at the documentation for the Encoding.GetEncoding(string) method, it says:
Parameters
name
Type: System.String
The code page name of the preferred encoding. Any value returned by the WebName property is valid. Possible values are listed in the Name column of the table that appears in the Encoding class topic.
So, you should be outputting the value of the WebName property in your WriteJson method if you want to be able to use this value to later reconstruct the Encoding in ReadJson.
Third, in your ReadJson method you are using reader.ReadAsString() to attempt to get the encoding name from the JSON. This will not work as you expect. When ReadJson is called by Json.Net, the reader is already positioned at the current value. When you call ReadAsString(), that advances the reader to the next token and then attempts to interpret that token as a string. What you really want to do is just get the value of the current token, which you can do using the Value property. Because Value is of type object, you will need to cast it to a string.
Here is the corrected code for the converter:
public class EncodingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Encoding).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((Encoding)value).WebName);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return Encoding.GetEncoding((string)reader.Value);
}
}
Fiddle: https://dotnetfiddle.net/UmLynX
Try:
public class CustomConverter : JsonConverter
{
public override bool CanConvert(System.Type objectType)
{
return true;// objectType.IsAssignableFrom(typeof(Encoding));
}
public override object ReadJson(JsonReader reader, System.Type objectType, object existingValue, JsonSerializer serializer)
{
return Encoding.GetEncoding(Convert.ToString(reader.Value));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var t = (Test)value;
var e = (Encoding)t.MyProperty;
writer.WriteValue(e.BodyName);
//serializer.Serialize(writer, e.BodyName);
}
}
And in Main:
var o = new Test { MyProperty = Encoding.UTF8 };
var s = new JsonSerializerSettings
{
Converters = new[] { new CustomConverter() }
};
var v = JsonConvert.SerializeObject(o, s);
var o2 = new Test();
o2.MyProperty = Encoding.GetEncoding(JsonConvert.DeserializeObject(v, s).ToString());

How can I use JSON.NET to handle a value that is sometimes an object and sometimes an array of the object?

I notice there are some other results on stackoverflow for this question but they don't seem to work or are vague. Using the most popular result, I have put together this:
The problem is that when JSON comes back and is being serialised into one of my custom types, one of the bits of JSON is sometimes an array, and sometimes just a string. If my custom type has a string, and the JSON is an array, I get an error. The same happens the other way around, if the JSON is an object and my custom type is an array. It just can't map it to the property.
I decided to solve this, I want to override the deserialisation of this particular property, and if it's an object, I want to convert it into an array of 1 object.
In the object I am serialising to, I added a JsonConverter which I think is going to override the way it's deserialised.
[JsonConverter(typeof(ArrayOrSingleObjectConverter<string>))]
public List<string> person { get; set; }
The idea is that the custom converter will convert a single object to an array. So if the JSON is "Hello" it will set person to be a List containing "Hello" instead of throwing an exception saying cannot convert string to List.
If it's already an array, it should just leave it alone.
The converter looks like this:
public class ArrayOrSingleObjectConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true; // Not sure about this but technically it can accept an array or an object, so everything is game.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(List<T>))
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
var singleObject = serializer.Deserialize<T>(reader);
var objectAsList = new List<T>();
objectAsList.Add(singleObject);
return objectAsList;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
It doesn't work. The above code throws an exception trying to deserialize a a single string saying it can't cast it into a List inside the if statement (the 'objectype' is however a List).
I'm struggling to understand exactly what the read and write methods are doing. In the other answer on stackoverflow, it suggests throwing a NotImplementedException in the read method. But if I do that, the read method is called and the exception throws.
I think I'm on the right track, but I need a nudge in the right direction. I think I'm a little confused about what the ReadJSon method is doing and what its parameters mean.
I don't really understand where the value is coming from that it's deserializing since I didn't specify it in the Deserialize method call.
I'm a bit out of my depth on this one.
I had to do something similar last week and I came up with the following, which works fine for a List rather than an array
internal class GenericListCreationJsonConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
T t = serializer.Deserialize<T>(reader);
return new List<T>(new[] { t });
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
I like this method which makes Json.NET do all the heavy lifting. And as a result, it supports anything that Json.NET supports (List<>, ArrayList, strongly-typed arrays, etc).
public class FlexibleCollectionConverter : JsonConverter
{
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)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize(reader, objectType);
}
var array = new JArray(JToken.ReadFrom(reader));
return array.ToObject(objectType);
}
public override bool CanConvert(Type objectType)
{
return typeof (IEnumerable).IsAssignableFrom(objectType);
}
}

Categories

Resources