skip to Main Content

I am trying to deserialize a complex nested json to my object.
The only thing is that in my object I use abstract types, so it must have some logic to use the correct derived class.

The types are saved in enums.

To explain it I will keep it fairly simple and do we only nest once, but for my purpose it is more nested with objects and types.

The json

{
   "screen":{
      "type":"Component",
      "footer":{
         "type":"Bar"
      },
      "header":{
         "type":"Top"
      }
   }
}

The classes

public abstract class Screen
{
    public abstract ScreenType Type { get; }
}

public enum ScreenType
{
    Component,
    b,
    c,
    d,
    e
}

public sealed class ComponentScreen : Screen
{
    public override ScreenType Type => ScreenType.Component;
    public Header? Header { get; init; }
    public Footer? Footer { get; init; }
    public bool? ShowStuff {get; init; }

}

public abstract class Header : ITyped<HeaderType>
{
    public abstract HeaderType Type { get; }
}

public enum HeaderType
{
    Top,
    b,
    c,
    d
}

public sealed class TopScreenHeader : Header
{
    public override HeaderType Type => HeaderType.Top;
    public string MyStuff { get; }
}

It isn’t possible to just change all the abstract types, or writing converters, since there are multiple abstract types with times X derived objects.
The JSON is also not consistent.

My current code using newtonsoft

var screen = JsonConvert.DeserializeObject<Screen>(jsonString, new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.Objects,
    ContractResolver = new CamelCasePropertyNamesContractResolver()

}

Which doesn’t work and gives errors:

Could not create an instance of type Screens.Screen. Type is an interace or abstract class and cannot be instantiated. Path 'screen', line 1, position 10.

2

Answers


  1. Not a direct answer but if you are considering migrating to .NET 7+ and System.Text.Jsonyou can use System.Text.Json‘s ability to handle type hierarchies:

    [JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
    [JsonDerivedType(typeof(ComponentScreen),"Component")]
    // all other Screen types ...
    public abstract class Screen
    {
        public abstract ScreenType Type { get; }
    }
    
    [JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
    [JsonDerivedType(typeof(TopScreenHeader), "Top")]
    // all other Header types ...
    public abstract class Header : ITyped<HeaderType>
    {
        public abstract HeaderType Type { get; }
    }
    

    Note that your json does not represent screen but some screen holder type so:

    class ScreenHolder
    {
        public Screen Screen { get; set; }
    }
    

    And usage (for footer I have used simple class):

    var js = """
    {
       "screen":{
          "type":"Component",
          "footer":{
             "type":"Bar"
          },
          "header":{
             "type":"Top"
          }
       }
    }
    """;
    
    var deserialize = JsonSerializer.Deserialize<ScreenHolder>(js, new JsonSerializerOptions{PropertyNameCaseInsensitive = true});
    

    As for Newtonsoft Json.NET there are following options:

    Also note that using TypeNameHandling with other option other than None and without proper SerializationBinder can lead to application vulnerabilities.

    Login or Signup to reply.
  2. you have a bug in you json convert code , you need add a root class. Also you need to include a class name in your json string to point what class to use to deserialize. To do it easier to create a base class for all your abstract classes

    public class Root
    {
        public ComponentScreen Screen { get; set; }
    }
    public class DataBase
    {
        public string ClassName { get { return this.GetType().Name; } }
    }
    public abstract class Footer : DataBase
    {
        public abstract HeaderType Type { get; }
    }
    
    public abstract class Header : DataBase
    {
        public abstract HeaderType Type { get; }
    }
    
    public sealed class ComponentScreen : Screen
    {
        public override ScreenType Type => ScreenType.Component;
        [JsonConverter(typeof( AbstractToConcretConverter))]
        public Header? Header { get; init; }
        [JsonConverter(typeof( AbstractToConcretConverter))]
        public Footer? Footer { get; init; }
        public bool? ShowStuff { get; init; }
    
    }
    

    it will create a json string of this kind

    {
       "screen":{
          "type":"Component",
          "footer":{
             "type":"Bar",
         "ClassName":"ScreenFooter"
          },
          "header":{
             "type":"Top",
          "ClassName":"TopScreenHeader"
          }
       }
    }
    
    

    you can use this converter to convert from base class to concrete one

    public class AbstractToConcretConverter : JsonConverter<DataBase>
    {
        public override DataBase ReadJson(JsonReader reader, Type objectType, DataBase existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            var jObj = JObject.Load(reader);
            return (DataBase)jObj.ToObject(Type.GetType((string)jObj["ClassName"]));
        }
    
        public override bool CanWrite => false;
        public override void WriteJson(JsonWriter writer, DataBase value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search