Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting interfaces for deserialization in JSON.NET

People also ask

How do I deserialize a JSON file?

A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.

How does JSON deserialization work?

In Deserialization, it does the opposite of Serialization which means it converts JSON string to custom . Net object. In the following code, it creates a JavaScriptSerializer instance and calls Deserialize() by passing JSON data. It returns a custom object (BlogSites) from JSON data.

What is deserialization of JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).


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