Home > Mobile >  Deserializing JSON with List of abstract class
Deserializing JSON with List of abstract class

Time:01-25

In my project i receive a JSON which represents a Configuration for an device. Each device has one or more Interfaces that it can be connected with. In the property "SupportedInterfaces" i receive a List with all Interfaces that are supported for this device. The List expects the type BaseInterface but i'll receive the derived classes "RestInterface", "ModbusTcpInterface" or more to come. I'm trying to deserialize List<BaseInterface> and directly convert it into the specific derived class type for further usage and to store in the DB, with the custom BaseInterfaceConverter. But that does not work like expected.

The following Code is my current state, i did not get it running - and for me it looks like it is because of the "nested" List inside the JSON that i'd like to deserialize. The Error in the Console is:

"Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray.

My code looks like this:

// The Parent Class
public class BaseInterface
{

 public string Name { get; set; }

 // This defines the specific interface-type 
 // TODO: change to an enumeration
 public string InterfaceType { get; set; }

 public string Option { get; set; }

}

// First Child
public class RestInterface : BaseInterface
{

 public string DefaultBaseUri { get; set; }

}


// 2nd Child
public class ModbusTcpInterface: BaseInterface
{

 public string IpAddress { get; set; }

 public int Port { get; set; }
}


// The Configuration which holds a list of Interfaces that i would like to parse while deserializing.

public class DeviceConfiguration 
{

 public string DeviceName { get; set; }

 public string DeviceManufacturer { get; set; }

 [JsonConverter(typeof(BaseInterfaceConverter))]
 public List<BaseInterface> SupportedInterfaces { get; set; }

 //... more props here

}

public class BaseInterfaceConverter : JsonConverter
{

  public override bool CanConvert(Type objectType)
  {
    return objectType == typeof(BaseInterface);
  }

  public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
  {
    JObject jobject = JObject.Load(reader);
    switch (jobject["InterfaceType"].Value<string>())
    {
      case "Rest":
        return JsonConvert.DeserializeObject<RestInterface>(jobject.ToString());
      case "ModbusTcp":
        return JsonConvert.DeserializeObject<ModbusTcpInterface>(jobject.ToString());
      default:
        throw new ArgumentException(String.Format("The Interfacetype {0} is not supported!", jobject["InterfaceType"].Value<string>()));
    }
  }

  public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
  {
    serializer.Serialize(writer, value);
  }
}

What is that i'm missing? Can anyone please give me a hint?

Edit added the JSON that I receive:

{
    "_id": "1234",
    "DeviceName": "First Configuration",
    "DeviceManufacturer": "eeeitschi",
    "SupportedInterfaces": [
        {
            "Name": "My first interface",
            "InterfaceType": "Rest",
            "Option": "option string here..",
            "DefaultBaseUri": "mybaseurl.io/itsme",
          },
          {
            "Name": "My second interface",
            "InterfaceType": "ModbusTcp",
            "Option": "option string here..",
            "IpAddress": "127.0.0.1",
            "Port": 502
          },
          {
            "Name": "My third interface",
            "InterfaceType": "Rest",
            "Option": "option string here..",
            "DefaultBaseUri": "base.url/api/devices",
          },
    ]
}

CodePudding user response:

You should be using JArray instead of JObject since SupportedInterfaces is an array not an object as the error says.

SupportedInterfaces looks like this in your JsonReader

[
        {
            "Name": "My first interface",
            "InterfaceType": "Rest",
            "Option": "option string here..",
            "DefaultBaseUri": "mybaseurl.io/itsme",
          },
          {
            "Name": "My second interface",
            "InterfaceType": "ModbusTcp",
            "Option": "option string here..",
            "IpAddress": "127.0.0.1",
            "Port": 502
          },
          {
            "Name": "My third interface",
            "InterfaceType": "Rest",
            "Option": "option string here..",
            "DefaultBaseUri": "base.url/api/devices",
          },
    ]

CodePudding user response:

You can try this code

var jObject = JObject.Parse(json);

DeviceConfiguration deviceConfiguration = jObject.ToObject<DeviceConfiguration>();

deviceConfiguration.SupportedInterfaces = ((JArray)jObject["SupportedInterfaces"])
    .Select(x => (string)x["InterfaceType"] == "Rest" ? (BaseInterface)x.ToObject<RestInterface>()
                                                : (BaseInterface)x.ToObject<ModbusTcpInterface>())
    .ToList();
                                                

but if you have an access to code that creates a json string, you can change it by using TypeNameHandling setting

    var jsonSerializerSettings = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.All
    };
var json = JsonConvert.SerializeObject(deviceConfiguration, jsonSerializerSettings);

In this case it will be much more simple code to deserialize

deviceConfiguration=JsonConvert.DeserializeObject<DeviceConfiguration>(json, jsonSerializerSettings);
  • Related