I've got a JSON coming in that I'd like to deserialize into a class A; one of the fields is an array of abstract class B (which has a few concrete implementations). Is it possible to deserialize the abstract-class array correctly without building a custom JsonConverter? Ideally, something native to JSON.net and not verbose, e.g. with TypeNameHandling.All... but I haven't got that particular route to work.
I do not have access to the JSON at serialization-time (if I did, TypeNameHandling would work great), but only at deserialization-time.
To be concrete:
public class A // the top-level class to deserialize
{
public B[] arr;
// other members...
}
public abstract class B
{
// ...some fields...
}
public class C : B
{
// a concrete implementation of B
}
public class D : B
{
// another concrete implementation of B
}
And I receive a JSON that may look like:
{
"arr" : [
{
// a C object
},
{
// a D object
}
]
}
I know you can effectively do something like this really cleanly with XML deserialization using the below; I'm pretty much looking for something similar in JSON.
// tells the deserializer to deserialize the object with field name "C" as a
// class C, and field name "D" as a D
[XmlElement("C", Type = typeof(C))]
[XmlElement("D", Type = typeof(D))]
public B[] arr;
Since you are unable to modify the JSON to use TypeNameHandling you need to create a custom converter that examines the properties to determine which type to return. Let's say you had your derived classes looking like this for example:
public class C : B
{
public string CProperty { get; set; }
}
public class D : B
{
public string DProperty { get; set; }
}
You could have a custom converter class that looks like this:
public class BConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type type,
object value, JsonSerializer serializer)
{
JObject jobject = JObject.Load(reader);
if (jobject.ContainsKey("CProperty"))
{
return jobject.ToObject<C>(serializer);
}
if (jobject.ContainsKey("DProperty"))
{
return jobject.ToObject<D>(serializer);
}
throw new Exception("Um, this is some other type!");
}
public override bool CanConvert(Type type) => type == typeof(B);
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> throw new NotImplementedException();
}
Now you can deserialise like this:
var result = JsonConvert.DeserializeObject<A>(json, new BConverter());
I'd like to serialize a property from a class into a json property, and when deserializing back, i'd like to read it to a different property.
Cannot figure out how to achieve this in json.net
Minimal example:
Backend converts a specially formatted json property into a numeric timestamp (firebase systemtimestamp).
I want to use the same abstract base class for both serialization and deserialization.
When serializing, i need to generate a "timestamp" json property with special values.
When deserializing, incoming json contains a number in "timestamp" property. I obviously need this to be read into a different class property.
Class:
[JsonObject(MemberSerialization.OptIn)]
public abstract class ItemBase
{
[JsonProperty("timestamp")]
private TimestampModel WriteTimestamp => TimestampModelInstance;
[JsonProperty("timestamp")]
private long ReadTimestamp { get; set; }
public DateTime Timestamp => DateTimeOffset.FromUnixTimeMilliseconds(ReadTimestamp).LocalDateTime;
}
I would need to somehow mark WriteTimestamp as "serialize only", and ReadTimestampas "deserialize only"
If anyone finds this useful, I resolved the issue with a custom JsonConverter.
This way I have only one class property that has the type of the read value (long), that always serializes into TimestampModelInstance (the special firebase structure) regardless of the actual value
Converter:
public class TimestampConverter : JsonConverter
{
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TimestampModel);
}
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)
{
JObject.FromObject(TimestampModelInstance).WriteTo(writer);
}
}
In Class:
[JsonProperty("timestamp")]
[JsonConverter(typeof(TimestampConverter))]
private long NumericTimestamp { get; set; }
public DateTime Timestamp =>
DateTimeOffset.FromUnixTimeMilliseconds(NumericTimestamp).LocalDateTime;
I have the following two example classes. Firstly the User class...
public class User : ILoadable
{
public User(string code)
{
this.Load(code);
}
public string Code { get; set; }
public UserGroup Group { get; set; }
}
... and the UserGroup class.
public class UserGroup : ILoadable
{
public UserGroup(string code)
{
this.Load(code);
}
public string Code { get; set; }
public int GroupId { get; set; }
// More properties
}
Then I have a method filling the object with data from a json file which is called from the constructors:
public static void Load(this ILoadable obj, string code)
{
string json = GetJsonFromCode(obj.GetType(), code);
JsonConvert.PopulateObject(json, obj);
}
What I want is not to save the User with its complete UserGroup property data, but only with its code, so it can be reconstructed by passing the code to the UserGroup constructor and getting the whole object from there. For example like this:
{
"UserCode": "Admin",
"Group": "Administrator"
}
I already tried creating a JsonConverter and setting it for the Group property with the following code...
[JsonProperty(ItemConverterType = typeof(StringObjectConverter)]
public UserGroup Group { get; set; }
... and converter:
class StringObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(ILoadable).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return Activator.CreateInstance(objectType, new object[] { (string)reader.Value });
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((ILoadable)value).Code);
}
}
But it does not seem to work, because everytime I try to load above seen json, the following exception is thrown:
Newtonsoft.Json.JsonSerializationException: 'Error converting value "Administrator" to type 'MyProject.UserGroup'. Path 'Group', line 2, position 10.'
Inner Exception
ArgumentException: Could not cast or convert from System.String to MyProject.UserGroup.
I could use a little help here, cause I don't know how get this to work, when even the converter does not change anything.
Rather than [JsonProperty(ItemConverterType = typeof(StringObjectConverter))] you must use [JsonConverter(typeof(StringObjectConverter))]:
public class User : ILoadable
{
public User(string code)
{
this.Load(code);
}
public string Code { get; set; }
[JsonConverter(typeof(StringObjectConverter))]
public UserGroup Group { get; set; }
}
Notes:
JsonConverter Instructs the JsonSerializer to use the specified JsonConverter when serializing the member or class.
You want to specify a converter to use for the UserGroup Group member, so this is the attribute to apply.
JsonPropertyAttribute.ItemConverterType specifies a JsonConverter used when serializing the property's collection items.
You would want to apply this attribute if you have a collection, say a List<UserGroup> Groups, and need to specify a converter for each item. Since you don't have a collection, this is not the correct attribute to use.
(Apparently when an ItemConverterType is applied to a member whose value is serialized as a JSON object, Json.NET applies the converter to every member of that object. This is a bit surprising; I can't find any place this is explicitly documented. This accounts for the exception you are seeing.)
Working .Net fiddle.
I want to decorate my classes with custom attributes, and read them when I convert to json using json.net inside a custom JsonConverter. I'll then vary the serialization depending on this custom attribute.
public class MyCustomJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//I want to get any attributes set on the property here.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Int64);
}
}
Another approach would be to specify my custom JsonConverter on the property using an attribute, but I don't want to do this because I want to inject some behaviour into the constructor of my custom JsonConverter by instantiating the converters in the JsonSerializer settings as below.
String json = JsonConvert.SerializeObject(new MyCLass(), new JsonSerializerSettings
{
Converters = new List
{
new MyCustomJsonConverter()
}
});
I can get to the name of the property in the textWriter path. And I can see some interesting hints in the documentation about Metadata, but I can't find a way to do this.
Here's an example decorated class:
public class MyCustomAttribute : Attribute { }
public class MyCLass
{
[MyCustom]
public Int64 MyInt { get; set; }
}
JsonConverters apply to types, not to fields or properties.
Instead of adding an attribute to a property that uses an existing type, consider creating a new type and writing a convert for that instead.
public struct MyCustomType
{
...
}
public class MyClass
{
public MyCustomType {get; set;}
}
Besides - in what other way would you ever want to serialize a raw integer? If the integer represents something, then create a struct or class for that something.
See also: "ValueObject" (Domain Driven Design fundamental concept)
Based on your comment below, an alternate approach would be to forget about JsonConverters and simply expose a secondary property:
public class MyClass
{
[JsonIgnore]
public Int64 MyInt {get; set;}
[JsonProperty("MyInt")]
public Int64 MyEncryptedInt
{
get { return Encrypt(MyInt); }
set { MyInt = Decrypt(value); }
}
}
I am trying to set up a reader that will take in JSON objects from various websites (think information scraping) and translate them into C# objects. I am currently using JSON.NET for the deserialization process. The problem I am running into is that it does not know how to handle interface-level properties in a class. So something of the nature:
public IThingy Thing
Will produce the error:
Could not create an instance of type IThingy. Type is an interface or abstract class and cannot be instantiated.
It is relatively important to have it be an IThingy as opposed to a Thingy since the code I am working on is considered sensitive and unit testing is highly important. Mocking of objects for atomic test scripts is not possible with fully-fledged objects like Thingy. They must be an interface.
I've been poring over JSON.NET's documentation for a while now, and the questions I could find on this site related to this are all from over a year ago. Any help?
Also, if it matters, my app is written in .NET 4.0.
#SamualDavis provided a great solution in a related question, which I'll summarize here.
If you have to deserialize a JSON stream into a concrete class that has interface properties, you can include the concrete classes as parameters to a constructor for the class! The NewtonSoft deserializer is smart enough to figure out that it needs to use those concrete classes to deserialize the properties.
Here is an example:
public class Visit : IVisit
{
/// <summary>
/// This constructor is required for the JSON deserializer to be able
/// to identify concrete classes to use when deserializing the interface properties.
/// </summary>
public Visit(MyLocation location, Guest guest)
{
Location = location;
Guest = guest;
}
public long VisitId { get; set; }
public ILocation Location { get; set; }
public DateTime VisitDate { get; set; }
public IGuest Guest { get; set; }
}
Why use a converter? There is a native functionality in Newtonsoft.Json to solve this exact problem:
Set TypeNameHandling in the JsonSerializerSettings to TypeNameHandling.Auto
JsonConvert.SerializeObject(
toSerialize,
new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto
});
This will put every type into the json, that is not held as a concrete instance of a type but as an interface or an abstract class.
Make sure that you are using the same settings for serialization and deserialization.
I tested it, and it works like a charm, even with lists.
Search Results
Web result with site links
⚠️ WARNING:
Only use this for json from a known and trusted source. User snipsnipsnip correctly mentioned that this is indeed a vunerability.
See CA2328 and SCS0028 for more information.
Source and an alternative manual implementation: Code Inside Blog
(Copied from this question)
In cases where I have not had control over the incoming JSON (and so cannot ensure that it includes a $type property) I have written a custom converter that just allows you to explicitly specify the concrete type:
public class Model
{
[JsonConverter(typeof(ConcreteTypeConverter<Something>))]
public ISomething TheThing { get; set; }
}
This just uses the default serializer implementation from Json.Net whilst explicitly specifying the concrete type.
An overview are available on this blog post. Source code is below:
public class ConcreteTypeConverter<TConcrete> : 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)
{
//explicitly specify the concrete type we want to create
return serializer.Deserialize<TConcrete>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//use the default serialization - it works fine
serializer.Serialize(writer, value);
}
}
Use this class, for mapping abstract type, to real type:
public class AbstractConverter<TReal, TAbstract>
: JsonConverter where TReal : TAbstract
{
public override Boolean CanConvert(Type objectType)
=> objectType == typeof(TAbstract);
public override Object ReadJson(JsonReader reader, Type type, Object value, JsonSerializer jser)
=> jser.Deserialize<TReal>(reader);
public override void WriteJson(JsonWriter writer, Object value, JsonSerializer jser)
=> jser.Serialize(writer, value);
}
and when deserialize:
var settings = new JsonSerializerSettings
{
Converters = {
new AbstractConverter<Thing, IThingy>(),
new AbstractConverter<Thing2, IThingy2>()
},
};
JsonConvert.DeserializeObject(json, type, settings);
To enable deserialization of multiple implementations of interfaces, you can use JsonConverter, but not through an attribute:
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Converters.Add(new DTOJsonConverter());
Interfaces.IEntity entity = serializer.Deserialize(jsonReader);
DTOJsonConverter maps each interface with a concrete implementation:
class DTOJsonConverter : Newtonsoft.Json.JsonConverter
{
private static readonly string ISCALAR_FULLNAME = typeof(Interfaces.IScalar).FullName;
private static readonly string IENTITY_FULLNAME = typeof(Interfaces.IEntity).FullName;
public override bool CanConvert(Type objectType)
{
if (objectType.FullName == ISCALAR_FULLNAME
|| objectType.FullName == IENTITY_FULLNAME)
{
return true;
}
return false;
}
public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
if (objectType.FullName == ISCALAR_FULLNAME)
return serializer.Deserialize(reader, typeof(DTO.ClientScalar));
else if (objectType.FullName == IENTITY_FULLNAME)
return serializer.Deserialize(reader, typeof(DTO.ClientEntity));
throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
DTOJsonConverter is required only for the deserializer. The serialization process is unchanged. The Json object do not need to embed concrete types names.
This SO post offers the same solution one step further with a generic JsonConverter.
Nicholas Westby provided a great solution in a awesome article.
If you want Deserializing JSON to one of many possible classes that implement an interface like that:
public class Person
{
public IProfession Profession { get; set; }
}
public interface IProfession
{
string JobTitle { get; }
}
public class Programming : IProfession
{
public string JobTitle => "Software Developer";
public string FavoriteLanguage { get; set; }
}
public class Writing : IProfession
{
public string JobTitle => "Copywriter";
public string FavoriteWord { get; set; }
}
public class Samples
{
public static Person GetProgrammer()
{
return new Person()
{
Profession = new Programming()
{
FavoriteLanguage = "C#"
}
};
}
}
You can use a custom JSON converter:
public class ProfessionConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IProfession);
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new InvalidOperationException("Use default serialization.");
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue,
JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var profession = default(IProfession);
switch (jsonObject["JobTitle"].Value())
{
case "Software Developer":
profession = new Programming();
break;
case "Copywriter":
profession = new Writing();
break;
}
serializer.Populate(jsonObject.CreateReader(), profession);
return profession;
}
}
And you will need to decorate the "Profession" property with a JsonConverter attribute to let it know to use your custom converter:
public class Person
{
[JsonConverter(typeof(ProfessionConverter))]
public IProfession Profession { get; set; }
}
And then, you can cast your class with an Interface:
Person person = JsonConvert.DeserializeObject<Person>(jsonString);
I found this useful. You might too.
Example Usage
public class Parent
{
[JsonConverter(typeof(InterfaceConverter<IChildModel, ChildModel>))]
IChildModel Child { get; set; }
}
Custom Creation Converter
public class InterfaceConverter<TInterface, TConcrete> : CustomCreationConverter<TInterface>
where TConcrete : TInterface, new()
{
public override TInterface Create(Type objectType)
{
return new TConcrete();
}
}
Json.NET documentation
Two things you might try:
Implement a try/parse model:
public class Organisation {
public string Name { get; set; }
[JsonConverter(typeof(RichDudeConverter))]
public IPerson Owner { get; set; }
}
public interface IPerson {
string Name { get; set; }
}
public class Tycoon : IPerson {
public string Name { get; set; }
}
public class Magnate : IPerson {
public string Name { get; set; }
public string IndustryName { get; set; }
}
public class Heir: IPerson {
public string Name { get; set; }
public IPerson Benefactor { get; set; }
}
public class RichDudeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPerson));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// pseudo-code
object richDude = serializer.Deserialize<Heir>(reader);
if (richDude == null)
{
richDude = serializer.Deserialize<Magnate>(reader);
}
if (richDude == null)
{
richDude = serializer.Deserialize<Tycoon>(reader);
}
return richDude;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Left as an exercise to the reader :)
throw new NotImplementedException();
}
}
Or, if you can do so in your object model, implement a concrete base class between IPerson and your leaf objects, and deserialize to it.
The first can potentially fail at runtime, the second requires changes to your object model and homogenizes the output to the lowest common denominator.
For those that might be curious about the ConcreteListTypeConverter that was referenced by Oliver, here is my attempt:
public class ConcreteListTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var res = serializer.Deserialize<List<TImplementation>>(reader);
return res.ConvertAll(x => (TInterface) x);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
No object will ever be an IThingy as interfaces are all abstract by definition.
The object you have that was first serialized was of some concrete type, implementing the abstract interface. You need to have this same concrete class revive the serialized data.
The resulting object will then be of some type that implements the abstract interface you are looking for.
From the documentation it follows that you can use
(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));
when deserializing to inform JSON.NET about the concrete type.
For what it's worth, I ended up having to handle this myself for the most part. Each object has a Deserialize(string jsonStream) method. A few snippets of it:
JObject parsedJson = this.ParseJson(jsonStream);
object thingyObjectJson = (object)parsedJson["thing"];
this.Thing = new Thingy(Convert.ToString(thingyObjectJson));
In this case, new Thingy(string) is a constructor that will call the Deserialize(string jsonStream) method of the appropriate concrete type. This scheme will continue to go downward and downward until you get to the base points that json.NET can just handle.
this.Name = (string)parsedJson["name"];
this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);
So on and so forth. This setup allowed me to give json.NET setups it can handle without having to refactor a large part of the library itself or using unwieldy try/parse models that would have bogged down our entire library due to the number of objects involved. It also means that I can effectively handle any json changes on a specific object, and I do not need to worry about everything that object touches. It's by no means the ideal solution, but it works quite well from our unit and integration testing.
Suppose an autofac setting like the following:
public class AutofacContractResolver : DefaultContractResolver
{
private readonly IContainer _container;
public AutofacContractResolver(IContainer container)
{
_container = container;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
JsonObjectContract contract = base.CreateObjectContract(objectType);
// use Autofac to create types that have been registered with it
if (_container.IsRegistered(objectType))
{
contract.DefaultCreator = () => _container.Resolve(objectType);
}
return contract;
}
}
Then, suppose your class is like this:
public class TaskController
{
private readonly ITaskRepository _repository;
private readonly ILogger _logger;
public TaskController(ITaskRepository repository, ILogger logger)
{
_repository = repository;
_logger = logger;
}
public ITaskRepository Repository
{
get { return _repository; }
}
public ILogger Logger
{
get { return _logger; }
}
}
Therefore, the usage of the resolver in deserialization could be like:
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<TaskRepository>().As<ITaskRepository>();
builder.RegisterType<TaskController>();
builder.Register(c => new LogService(new DateTime(2000, 12, 12))).As<ILogger>();
IContainer container = builder.Build();
AutofacContractResolver contractResolver = new AutofacContractResolver(container);
string json = #"{
'Logger': {
'Level':'Debug'
}
}";
// ITaskRespository and ILogger constructor parameters are injected by Autofac
TaskController controller = JsonConvert.DeserializeObject<TaskController>(json, new JsonSerializerSettings
{
ContractResolver = contractResolver
});
Console.WriteLine(controller.Repository.GetType().Name);
You can see more details in http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm
My solution to this one, which I like because it is nicely general, is as follows:
/// <summary>
/// Automagically convert known interfaces to (specific) concrete classes on deserialisation
/// </summary>
public class WithMocksJsonConverter : JsonConverter
{
/// <summary>
/// The interfaces I know how to instantiate mapped to the classes with which I shall instantiate them, as a Dictionary.
/// </summary>
private readonly Dictionary<Type,Type> conversions = new Dictionary<Type,Type>() {
{ typeof(IOne), typeof(MockOne) },
{ typeof(ITwo), typeof(MockTwo) },
{ typeof(IThree), typeof(MockThree) },
{ typeof(IFour), typeof(MockFour) }
};
/// <summary>
/// Can I convert an object of this type?
/// </summary>
/// <param name="objectType">The type under consideration</param>
/// <returns>True if I can convert the type under consideration, else false.</returns>
public override bool CanConvert(Type objectType)
{
return conversions.Keys.Contains(objectType);
}
/// <summary>
/// Attempt to read an object of the specified type from this reader.
/// </summary>
/// <param name="reader">The reader from which I read.</param>
/// <param name="objectType">The type of object I'm trying to read, anticipated to be one I can convert.</param>
/// <param name="existingValue">The existing value of the object being read.</param>
/// <param name="serializer">The serializer invoking this request.</param>
/// <returns>An object of the type into which I convert the specified objectType.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
return serializer.Deserialize(reader, this.conversions[objectType]);
}
catch (Exception)
{
throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
}
}
/// <summary>
/// Not yet implemented.
/// </summary>
/// <param name="writer">The writer to which I would write.</param>
/// <param name="value">The value I am attempting to write.</param>
/// <param name="serializer">the serializer invoking this request.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
You could obviously and trivially convert it into an even more general converter by adding a constructor which took an argument of type Dictionary<Type,Type> with which to instantiate the conversions instance variable.
Several years on and I had a similar issue. In my case there were heavily nested interfaces and a preference for generating the concrete classes at runtime so that It would work with a generic class.
I decided to create a proxy class at run time that wraps the object returned by Newtonsoft.
The advantage of this approach is that it does not require a concrete implementation of the class and can handle any depth of nested interfaces automatically. You can see more about it on my blog.
using Castle.DynamicProxy;
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;
namespace LL.Utilities.Std.Json
{
public static class JObjectExtension
{
private static ProxyGenerator _generator = new ProxyGenerator();
public static dynamic toProxy(this JObject targetObject, Type interfaceType)
{
return _generator.CreateInterfaceProxyWithoutTarget(interfaceType, new JObjectInterceptor(targetObject));
}
public static InterfaceType toProxy<InterfaceType>(this JObject targetObject)
{
return toProxy(targetObject, typeof(InterfaceType));
}
}
[Serializable]
public class JObjectInterceptor : IInterceptor
{
private JObject _target;
public JObjectInterceptor(JObject target)
{
_target = target;
}
public void Intercept(IInvocation invocation)
{
var methodName = invocation.Method.Name;
if(invocation.Method.IsSpecialName && methodName.StartsWith("get_"))
{
var returnType = invocation.Method.ReturnType;
methodName = methodName.Substring(4);
if (_target == null || _target[methodName] == null)
{
if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
{
invocation.ReturnValue = null;
return;
}
}
if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
{
invocation.ReturnValue = _target[methodName].ToObject(returnType);
}
else
{
invocation.ReturnValue = ((JObject)_target[methodName]).toProxy(returnType);
}
}
else
{
throw new NotImplementedException("Only get accessors are implemented in proxy");
}
}
}
}
Usage:
var jObj = JObject.Parse(input);
InterfaceType proxyObject = jObj.toProxy<InterfaceType>();
Use this JsonKnownTypes, it's very similar way to use, it just add discriminator to json:
[JsonConverter(typeof(JsonKnownTypeConverter<Interface1>))]
[JsonKnownType(typeof(MyClass), "myClass")]
public interface Interface1
{ }
public class MyClass : Interface1
{
public string Something;
}
Now when you serialize object in json will be add "$type" with "myClass" value and it will be use for deserialize
Json:
{"Something":"something", "$type":"derived"}
My solution was added the interface elements in the constructor.
public class Customer: ICustomer{
public Customer(Details details){
Details = details;
}
[JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
public IDetails Details {get; set;}
}
You can also use custom TextInputFormatter, no external libraries needed, also helps you gain insight on how you can handle (de)serialization of any type of data.
public class MyInputTypeFormatter : TextInputFormatter
{
public MyInputTypeFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
SupportedEncodings.Add(Encoding.UTF8);
}
protected override bool CanReadType(Type type)
{
return type == typeof(MyClass);
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<ImageTypeConverter>>();
using var reader = new StreamReader(httpContext.Request.Body, encoding);
{
var data = await reader.ReadToEndAsync();
if (data.Contains("Hello"))
{
var myClass= new MyClass(data);
return await InputFormatterResult.SuccessAsync(myClass);
}
else
{
return await InputFormatterResult.FailureAsync();
}
}
}
}
Then, simply add this input formatter to the list of input formatters with
services.AddControllers(options=> {
options.InputFormatters.Insert(0, new MyInputFormatter());
});
0 here means this is the first input formatter invoked when model binding.
It seems like a lot of work but most of it is just boilerplate.
I will explain how this works,
You have an action method/ route which has a parameter of MyClass type. When a request comes to it, your input formatter's CanReadType is invoked and it returns true meaning it will handle the deserialization.Then the ReadRequestBodyAsync method is invoked and the request data is given to it.
You can do whatever you want with the data and return an object of type MyClass if your deserialization succeeds. Else you just return a failure.
In the deserialization you can use
using (JsonDocument document = JsonDocument.Parse(jsonString))
{
JsonElement root = document.RootElement;
// ...
}
You can traverse elements as the input is parsed into a json object and then held into a DOM. Then you can see what they contain and manually create classes with their data and convert your input-as-interfaces into classes.
Note: JsonDocument was introduced in .Net 3.1
You can check out how to use it here
More about how to use TextInputFormatter and TextOutputFormatter
The benefit of using a custom input formatter is that it provides a central class for handling your custom classes which may use multiple interfaces. It also gives you fine control over handling the input data.