Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

213
Views
Using Newtonsoft.JSON custom converters to read json with different input

I am using NewtonSoft.Json to read/write our data as json. One (very simplified) example of this is:

{
  "$type": "MyNamespace.LandingEvent, MyAssembly",
  "TimeOfLanding": "2021-04-11T15:00:00.000Z",
  "AirportName": "KLAX",
  "AirportRunway": "25L"
}

With a C# DTO class that mimicks the properties. Note that we use TypeNameHandling.

We want to change our C# class to a more complex setup:

class Airport
{
    public string Name { get; set; }
    public string Runway { get; set; }
}

class LandingEvent
{
    public DateTime TimeOfLanding { get; set; }
    public Airport Airport { get; set; }
}

which will result in, that new data will be written to JSON as:

{
  "$type": "MyNamespace.LandingEvent, MyAssembly",
  "TimeOfLanding": "2021-04-11T15:00:00.000Z",
  "Airport": {
    "Name": "KLAX",
    "Runway": "25L"
  }
}

But we still need to be able to read the old JSON data and parse into the new class structure. And this is what I currently struggle with.

I know that the way to go is probably a specialized JsonConverter. I have a couple of questions in this regard:

  1. How do I read the $type property and instantiate the right type? (my overriden CanConvert() method is fed the name of a base-class (due to the real context being more complex than this example).
  2. I only want to do custom read, if the property AirportName exsist. How do I fall-back to default deserialization, if this is not the case?

Edit: Some clarification is in order. If I create a custom JsonConverter, then CanConvert will receive the type EventBase, but the $type can actually contain either "MyNamespace.LandingEvent, MyAssembly" or "MyNamespace.TakeoffEvent, MyAssembly". Therefore I will probably need to instantiate the returned object myself based on this value. I am not sure how, though.

over 4 years ago · Santiago Trujillo
2 answers
Answer question

0

You can use a custom JsonConverter to do double duty in handling both the polymorphic event types and the varying JSON formats. Below is an example. It works by loading the data into a JObject, where it can read the $type property and instantiate the correct event type. From there, it will try to populate the event object from the JSON. If the Airport fails to deserialize, it will then attempt to read the legacy airport proprties and populate a new Airport instance from that.

class EventConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(BaseEvent).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);

        string type = (string)obj["$type"];
        BaseEvent baseEvent;
        if (type.Contains(nameof(TakeoffEvent)))
        {
            baseEvent = new TakeoffEvent();
        }
        else
        {
            baseEvent = new LandingEvent();
        }
        serializer.Populate(obj.CreateReader(), baseEvent);

        if (baseEvent.Airport == null)
        {
            baseEvent.Airport = new Airport
            {
                Name = (string)obj["AirportName"],
                Runway = (string)obj["AirportRunway"]
            };
        }
        return baseEvent;
    }

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Note: this assumes your class structure actually looks like this:

class Airport
{
    public string Name { get; set; }
    public string Runway { get; set; }
}

class BaseEvent
{
    public Airport Airport { get; set; }
}

class TakeoffEvent : BaseEvent
{
    public DateTime TimeOfTakeoff { get; set; }
}

class LandingEvent : BaseEvent
{
    public DateTime TimeOfLanding { get; set; }
}

To use the converter, add it to the Converters collection in the JsonSerializerSettings, and pass the settings to DeserializeObject():

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects,
    Converters = new List<JsonConverter> { new EventConverter() }
};

var baseEvent = JsonConvert.DeserializeObject<BaseEvent>(json, settings);

Here is a working demo: https://dotnetfiddle.net/jSaq4T

See also: Adding backward compatibility support for an older JSON structure

over 4 years ago · Santiago Trujillo Report

0

Classes change, this kind of Json strings change and will get extra features in future versions. You'll keep adjusting your declarations. With Newtonsoft, you can add custom handlers for varying class inheritance and keep using deserialize, but you'll have to maintain that code.

For dynamic Json, I find it easier to use JObject, JArray and JToken instead, to freely parse a Json string. Especially if you're only interested in some of the fields.

I can only give you an example, I think this is (a little) related to your project, but not the same part (smiley)

I use below code to decode part of a glTF 3d-object file produced by Blender in MSFS-converted format. This Json-like format consists of sections. Each Json section looks something like this,

  "asset" : {
    "extensions" : {
        "ASOBO_normal_map_convention" : {
            "tangent_space_convention" : "DirectX"
        }
    },
    "generator" : "Extended Khronos glTF Blender I/O v1.0.0",
    "version" : "2.0"
  },

.. but these sections and their fields are mostly optional and in some GLtf's they are not filled in. It is not "serializable or deserializable" to classes.

I declare some

    public JObject AssetObject;

.. filling it in from Json string sJson as follows:

        dynamic stuff = JObject.Parse(sJson);
        var pp = stuff.Children();
        Dictionary<string, bool> d = new Dictionary<string, bool>();
        foreach (JProperty jo in pp) d[jo.Name] = true; // all sections

        string cSection= "asset";
        if (!d.ContainsKey(cSection)) { LogLine(98, "Warning: BPG Json has no " + cSection + " section."); return false; }
        else
        {
            AssetObject = (JObject)stuff[cSection];
            ParseGLBAsset();
        }

Notice the use of a dynamic declaration at first, a section will land in JObject via cast. I store the various parts of the section into string properties. The parse itself takes place in ParseGLBAsset(), this function looks as follows:

    public void ParseGLBAsset()
    {
        foreach (JProperty jbp in AssetObject.Children())
            if (jbp.Name == "generator")
            { GLBGenerator = jbp.Value.ToString(); }
            else
            if (jbp.Name == "extensions")
            {
                GLBAssetExtensions = jbp.Value.ToString(); 
                LogLine(0, "Asset extensions: " + GLBAssetExtensions);
            }
            else
              if (jbp.Name == "version")
            { GLBVersion = jbp.Value.ToString(); }
        LogLine(1, "Found asset.generator=" + GLBGenerator);
        LogLine(1, "Found asset.version=" + GLBVersion);

    }
over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!