3

I'm struggling with deserialization of the json file using the newtonsoft.json.
Object which I want to deserialize looks like this:

public class Device
{
        public string Name { get; set; }
        public int Id { get; set; }
        public string Type { get; set; }

        public List<Sensor> Sensors { get; }

        public bool IsPaired { get; set; }

}

Sensor class is Virtual.
I have multiple classes which inherit from Sensor class (TemperatureSensor, WaterLevelSensor etc.) and add some new properties. Instances of these classes are stored in Sensors collection.

Json file looks like this:

[  
   {  
      "Name":"Device1",
      "Id":0,
      "Type":"TemperatureSensor",
      "Sensors":[  
         {  
            "Id":0,
            "Type":"TemperatureSensor",
            "Data":18.136218099999997,
            "ReadIntervalInMiliseconds":5000
         },
         {  
            "Id":1,
            "Type":"TemperatureSensor",
            "Data":18.0999819,
            "ReadIntervalInMiliseconds":5000
         }
      ],
      "IsPaired":false
   },
   {  
      "Name":"Device2",
      "Id":1,
      "Type":"AutomaticGate",
      "Sensors":[  
         {  
            "OpenPercentage":0,
            "Id":0,
            "Type":"AutomaticGate",
            "Data":0.0,
            "ReadIntervalInMiliseconds":0
         }
      ],
      "IsPaired":false
   },
   {  
      "Name":"Device3",
      "Id":2,
      "Type":"Other",
      "Sensors":[  
         {  
            "IsActive":false,
            "Id":0,
            "Type":"AirConditioner",
            "Data":0.0,
            "ReadIntervalInMiliseconds":0
         },
         {  
            "Id":1,
            "Type":"LightSensor",
            "Data":4.0,
            "ReadIntervalInMiliseconds":5000
         }
      ],
      "IsPaired":false
   }
]

I assume that i have to read the "Type" of Sensor from json file, and on this basis create the Object and add it to some collection and then return Device class object with this collection.

I was trying to make custom JsonConverter like in this blog post but with little effect.

HoneyBunny
  • 33
  • 5
  • 2
    A JsonConverter is the way to go. Do you have an example of what you've tried regarding the JsonConverter? – Jesse de Wit Aug 13 '19 at 12:49
  • Hmm unfortunately I don't have. I think i've remember what was the point. I didn't known how to extract this collection object from json and then check it's "Type" property. I think i use something like: JToken sensors = jObject["Sensors"]; – HoneyBunny Aug 13 '19 at 13:07
  • Possible duplicate of [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/questions/19307752/deserializing-polymorphic-json-classes-without-type-information-using-json-net) – Heretic Monkey Aug 13 '19 at 13:45

2 Answers2

3

You can create a custom JsonConverter to convert Sensor objects to concrete derived classes. Here's a working example of such a JsonConverter:

public class SensorConverter : JsonConverter
{
    public override bool CanRead => true;
    public override bool CanWrite => false;
    public override bool CanConvert(Type objectType)
    {
        // Don't do IsAssignableFrom tricks here, because you only know how to convert the abstract class Sensor.
        return objectType == typeof(Sensor);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jObject = JObject.Load(reader);
        string sensorType = jObject["Type"].Value<string>();
        switch (sensorType)
        {
            case "TemperatureSensor":
                return jObject.ToObject<TemperatureSensor>(serializer);
            case "AutomaticGate":
                return jObject.ToObject<AutomaticGate>(serializer);
            case "AirConditioner":
                return jObject.ToObject<AirConditioner>(serializer);
            case "LightSensor":
                return jObject.ToObject<LightSensor>(serializer);
            default:
                throw new NotSupportedException($"Sensor type '{sensorType}' is not supported.");
        }
    }

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

Then, when deserializing, you will have to add your custom converter to the settings in order for this to work.

Note that your Sensors property is get-only at the moment. You will have to provide a setter in order for NewtonSoft to populate the property.

Jesse de Wit
  • 3,867
  • 1
  • 20
  • 41
  • 1
    @HoneyBunny - you could also create a base Sensor class that has properties you know every sensor will have (i.e. id, type, etc). Then in the **default** case in Jesse de Wit's example you could still parse/map the result to base if you needed to still work with the result. – Stinky Towel Aug 13 '19 at 13:47
0

Another solution that requires much less code is using JsonSubTypes

Assuming an abstract Sensor class, you need to register via custom attribute a known subclass and it's identifier. So in your case, the identifier is property named "Type" and the class mappings is in KnownSubType attributes.

[JsonConverter(typeof(JsonSubtypes), "Type")]
[JsonSubtypes.KnownSubType(typeof(TemperatureSensor), "TemperatureSensor")]
[JsonSubtypes.KnownSubType(typeof(WaterLevelSensor), "WaterLevelSensor")]
[JsonSubtypes.KnownSubType(typeof(AirConditioner), "AirConditioner")]
[JsonSubtypes.KnownSubType(typeof(AutomaticGate), "AutomaticGate")]
[JsonSubtypes.KnownSubType(typeof(LightSensor), "LightSensor")]
public abstract class Sensor
{
}

In your Device class, Sensors property must have a set property.

public List<Sensor> Sensors { get; set;}

Usage:

var items = JsonConvert.DeserializeObject<List<Device>>(json);
EylM
  • 5,967
  • 2
  • 16
  • 28