How can I emulate deserialization "callbacks" in System.Text.Json? - c#

I am attempting to have my object execute some logic immediately after deserialization. I am using the example shown on the following Microsoft documentation
The issue that I'm running into is that calling JsonSerializer.Deserialize() results in an infinite recursion loop because Deserialize() is called in the Read override. I know that I can get around this by implementing my own deserialize method - but that defeats the purpose of using this override, so I'm assuming that there's gotta be some way to keep the Deserialize() call in the Read override.
My deserialization test
public void TestSerializationConverterCallbacks()
{
Animal a = JsonSerializer.Deserialize<Animal>(SerializedData.SerializedAnimal);
}
My serialized object
[JsonConverter(typeof(AnimalCallbacksConverter))]
public class Animal
{
public string Name { get; set; } = "dog";
public string Id { get; set; } = "123";
}
My custom Serializer
public class AnimalCallbacksConverter : JsonConverter<Animal>
{
public override Animal Read(
ref Utf8JsonReader reader,
Type type,
JsonSerializerOptions options)
{
// Place "before" code here (OnDeserializing),
// but note that there is no access here to the POCO instance.
Console.WriteLine("OnDeserializing");
// Don't pass in options when recursively calling Deserialize.
Animal animal = JsonSerializer.Deserialize<Animal>(ref reader);
// Place "after" code here (OnDeserialized)
Console.WriteLine("OnDeserialized");
return animal;
}
public override void Write(
Utf8JsonWriter writer,
Animal animal, JsonSerializerOptions options)
{
// Place "before" code here (OnSerializing)
Console.WriteLine("OnSerializing");
// Don't pass in options when recursively calling Serialize.
JsonSerializer.Serialize(writer, animal);
// Place "after" code here (OnSerialized)
Console.WriteLine("OnSerialized");
}
}

You can use actual deserialization callbacks...
public class MyObject : IJsonOnDeserialized
{
public MyProperty1 { get; set; }
public MyProperty2 { get; set; }
void IJsonOnDeserialized.OnDeserialized()
{
//Code runs after deserialization is complete
}
}
System.Text.Json has an example of this about 2/3rds the way down the page. Newtonsoft.Json doesn't have the limitations that System.Text.Json has, and can use [OnDeserializing] and [OnDeserialized] attributes that take a streaming context.

Related

C# how can I access the object properties that not exist in the implemented interface

Please consider the following code. I have an interface named IClass.
public interface IClass
{
public string MyProperty { get; set; }
}
And I have two classes that implements this interface:
public class ClassA : IClass
{
public string MyProperty { get; set; }
}
public class ClassB : IClass
{
public string MyProperty { get; set; }
public string MyProperty2 { get; set; }
}
And finally I have a method that returns IClass interface:
public static IClass MethodA()
{
return new ClassB { MyProperty = "A", MyProperty2 = "B" };
}
My problem is I can't access the MyProperty2 from the MethodA
static void Main(string[] args)
{
Console.WriteLine(JsonSerializer.Serialize(MethodA()));
}
It is only returning {"MyProperty":"A"} How can I actually get the MyProperty2 without using polymorphic model binding? Is there any other ways?
You need to cast
Console.WriteLine(JsonSerializer.Serialize((ClassB) MethodA()))
However, doing so kind of defeats the purpose of using an interface in the first place.
Probably you will have to check and cast it explicitly like
var data = MethodA();
JsonSerializer.Serialize(data is ClassB ? data as ClassB : data as ClassA )
Serialization is based off of the type information you supply. In the case of the generic overloads, this is the generic type (the T in Serialize<T>). You are calling one of the generic overloads and type-inference is picking up IClass. To get around this, use the overload that accepts a Type:
var obj = MethodA();
var json = JsonSerializer.Serialize(obj, obj.GetType(), default);
Or explicitly cast to object so that the generic type is inferred as object:
var json = JsonSerializer.Serialize((object)MethodA(), default);
Or, a semi-combination of the two:
var json = JsonSerializer.Serialize(MethodA(), typeof(object), default);
If you don't want this behavior from the serializer, you would have to cast to the explicit type (assuming you know it and/or can live with a bunch of type checks)
var json = JsonSerializer.Serialize((ClassB) MethodA(), default);
Note that the object solution will only work at the top-level/root object. If you have a property somewhere in your object graph that is declared as IClass it won't serialize how you want. You'd unfortunately have to declare those properties with the object type.
The alternative would be writing a custom converter for your interface type and reflecting over the properties yourself. While overkill for a root object (given the solutions above) it would allow you to handle nested objects of your interface type. A simple converter could delegate to the object's real type.
public class IClassConverter : JsonConverter<IClass>
{
public override IClass Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) => throw new NotImplementedException();
public override void Write(
Utf8JsonWriter writer,
IClass obj,
JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, obj, obj.GetType(), default); // don't pass options, will result in endless loop
}
}
You then use it by adding your converter to the Serializer Options:
var opts = new JsonSerializerOptions();
opts.Converters.Add(new IClassConverter());
IClass obj = MethodA();
var json = JsonSerializer.Serialize(obj, opts);

Populate object with non-primitive property as String from JSON

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.

Double included object while serializing collection class

I'm trying to serialize class Poll, which looks like that:
class Poll
{
(...) //methods
public AnswersCollection answers { get; set; }
public TagsCollection tags { get; set; }
public string question { get; set; }
}
As you can see, I have "TagsCollection" and "AnswersCollection", which both look pretty similiar, so I'll show only one of them.
class AnswersCollection
{
(...) //methods
public List<Answer> answers { get; set; }
}
And, finnaly, Answer class.
class Answer
{
(...) //methods
public string name { get; set; }
public uint voteQuantity { get; set; }
}
All clases have default public constructor (without parameters) so JSON.NET doesn't have any problems with serialization.
Problem is with AnswersCollection (which is encapsulation), because of it, JSON output looks like that:
{
"answers":{
"answers":[
{
"name":"Foo",
"voteQuantity":45
},
{
"name":"Bar",
"voteQuantity":30
}
]
},
"tags":{
"tags":[
{
"name":"FooTag",
"id":5
},
{
"name":"BarTag",
"id":4
}
]
},
"question":"Question?"
}
As you can see, the problem is with structures like "answers":{ "answers": [(...)] }
Is there option to serialize it to structures like "answers" :[(...)] without second "answers" tag?
I tried to use properties like "isReference" but it didn't worked.
The serialization is actually doing exactly what is expected of it.
I know it will not exactly answer your original question as to whether there is a way to ask Json.Net to do what you want, but your best option here would be to have inheritence instead of composition for your AnswersCollection.
Since the name suggests the class is a collection of answers, why not make it inherit from List<Answer> instead of having a property of this type ?
If you really can't change the structure of your object, you could go the long-hard way, and implement your own JsonConverter to have an absolute control of how your properties are going to be serialized / deserialized.
You would have to apply the JsonConverterAttribute to the Poll.answers property to tell Json.Net to use your custom serializer.
However, I would highly recommend not taking that approach, if you can avoid it.
Off-topic as a side note:
you should consider using a CamelCasePropertyNamesContractResolver to tell your serializer to use CamelCasing when serializing your properties, so you don't have to use camelCasing on your property itself: answers should be spelled Answers if you want to follow common naming practices.
If you don't need to go round-trip with your serialization (in other words, you just need to serialize but not deserialize), then one easy way to get the result you want is to make your collection classes implement IEnumerable<T> like this:
class AnswersCollection : IEnumerable<Answer>
{
public List<Answer> answers { get; set; }
public IEnumerator<Answer> GetEnumerator()
{
return answers != null ? answers.GetEnumerator() : new List<Answer>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
This works because Json.Net automatically treats classes that implement IEnumerable as arrays during serialization.
If you need to deserialize as well, then you'll need to go a bit further and do one of the following:
Add a constructor that accepts an IEnumerable<T>, as suggested by #dbc in the comments below;
Make your class implement ICollection<T>, or
Just make your Collection classes inherit from List<T> as #Fabio Salvalai suggested in his answer.
Another alternative, if you don't want to change your classes, is to implement a custom JsonConverter to handle the custom serialization/deserialization. The converter might look something like this:
class CollectionConverter<TCollection, TItem> : JsonConverter where TCollection : new()
{
private string ListPropertyName { get; set; }
public CollectionConverter(string listPropertyName)
{
ListPropertyName = listPropertyName;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TCollection);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<TItem> list = (List<TItem>)typeof(TCollection).GetProperty(ListPropertyName).GetValue(value, null);
JArray array = JArray.FromObject(list);
array.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);
List<TItem> list = array.ToObject<List<TItem>>();
TCollection collection = new TCollection();
typeof(TCollection).GetProperty(ListPropertyName).SetValue(collection, list);
return collection;
}
}
To use the converter, you would need to add [JsonConverter] attributes to the Collection properties in your Poll class like this:
class Poll
{
[JsonConverter(typeof(CollectionConverter<AnswersCollection, Answer>), "answers")]
public AnswersCollection answers { get; set; }
[JsonConverter(typeof(CollectionConverter<TagsCollection, Tag>), "tags")]
public TagsCollection tags { get; set; }
public string question { get; set; }
}
Then just serialize and deserialize as usual.

Can I get the attributes on a property from the WriteJson method of a custom JsonConverter?

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); }
}
}

Casting interfaces for deserialization in JSON.NET

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.

Categories

Resources