0

I would like to serialize the list of derived objects to json and to deserialize. Serialization works fine, the deserialize fails to convert the objects to derived class objects, all objects are deserialized to base class objects and lose members that belong to the derived class. I have base class AutoEvent, and derived classes MouseClickEvent and ClickImageEvent, so once i deserialize to List all objects are of type AutoEvent. How can i deserialize so that i can convert each object to the derived class object?

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Web.Script.Serialization;

namespace AutoEvent
{            
    public class AutoEvent
    {
        public string Name { get; set; }
        public int DelayMs { get; set; }
        public AutoEvent(string name, int delayMs)
        {
            Name = name;
            DelayMs = delayMs;
        }
        public AutoEvent() { }
    }

    public class MouseClickEvent : AutoEvent
    {
        public Rectangle RectArea { get; set; }
        public bool IsDoubleClick { get; set; }
        public MouseClickEvent(string name, int delayMs, Rectangle rectArea, bool isDoubleClick) : base(name, delayMs)
        {
            RectArea = rectArea;
            IsDoubleClick = isDoubleClick;
        }
        public MouseClickEvent() { }

    }

    public class ClickImageEvent : AutoEvent
    {
        public List<string> ImgFiles { get; set; }
        public Rectangle SearchArea { get; set; }
        public double ImgTolerance { get; set; }
        public double ImgError { get; set; }
        public bool IsDoubleClick { get; set; }
        public ClickImageEvent(string name, int delayMs, Rectangle searchArea, bool isDoubleClick, double imgTol, double imgErr) : base(name, delayMs)
        {
            SearchArea = searchArea;
            IsDoubleClick = isDoubleClick;
            ImgTolerance = imgTol;
            ImgError = imgErr;
        }
        public ClickImageEvent() { }

    }

    class Program
    {
        static void Main(string[] args)
        {
            string path = @"c:/nenad/testSer.txt";
            List<AutoEvent> events = new List<AutoEvent>();
            MouseClickEvent clickEvent1 = new MouseClickEvent("mouse click1", 100, new Rectangle(20, 30, 15, 10), true);
            MouseClickEvent clickEvent2 = new MouseClickEvent("mouse click2", 15, new Rectangle(20, 45, 15, 10), true);
            ClickImageEvent imgclick1 = new ClickImageEvent("image click1", 15, new Rectangle(20, 45, 555, 150), false, 0.1, 0.05);
            ClickImageEvent imgclick2 = new ClickImageEvent("image click2", 125, new Rectangle(2220, 45, 5525, 150), false, 0.15, 0.25);
            events.Add(clickEvent1);
            events.Add(clickEvent2);
            events.Add(imgclick1);
            events.Add(imgclick2);

            JavaScriptSerializer ser = new JavaScriptSerializer();
            string json = ser.Serialize(events);
            if (!File.Exists(path))
            {
                using (var h = File.Create(path)) ;
            }

            File.WriteAllText(path, json);

            json = File.ReadAllText(path);
            events = ser.Deserialize<List<AutoEvent>>(json);
            MouseClickEvent event1 = (MouseClickEvent)events[0]; // fails at runtime to convert 
            ClickImageEvent event2 = (ClickImageEvent)events[2]; // fails at runtime to convert 
        }
    }
}
Nenad Birešev
  • 377
  • 2
  • 10
  • This doesn't work because events[0] is of type AutoEvent, so it doesn't contain members like RectArea and IsDoubleClick but only Name and DelayMs. – Nenad Birešev Nov 30 '21 at 16:35

2 Answers2

0

From the documentation about javascriptserializer

For .NET Framework 4.7.2 and later versions, use the APIs in the System.Text.Json namespace for serialization and deserialization. For earlier versions of .NET Framework, use Newtonsoft.Json. This type was intended to provide serialization and deserialization functionality for AJAX-enabled applications

I.e. you should probably be using another json library.

If you prefer Json.Net, See Json.net serialize/deserialize derived types?. If you prefer System.Text.Json, see How to serialize properties of derived classes with System.Text.Json

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • What is the difference what method for serialization i use, the issue is still there, that the information about members of derived objects is lost after deserialization? – Nenad Birešev Nov 30 '21 at 16:33
  • @Nenad Birešev when serializing the library needs to include all the data for the derived type, and also the actual type of the object. This type information needs to be used during deserialization to provide the correct type of object. So using an appropriate library is absolutely needed. – JonasH Nov 30 '21 at 16:38
  • Can you please provide simple example? – Nenad Birešev Nov 30 '21 at 17:36
  • @Nenad Birešev there should be examples if you follow the links, or do some googling on your library and polymorphism. – JonasH Dec 01 '21 at 07:57
  • It worked with Newtonsoft.Json by using settings: JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; – Nenad Birešev Dec 01 '21 at 08:08
0

when you run

  events = ser.Deserialize<List<AutoEvent>>(json);

you can get only this

[
  {
    "Name": "mouse click1",
    "DelayMs": 100
  },
  {
    "Name": "mouse click2",
    "DelayMs": 15
  },
  {
    "Name": "image click1",
    "DelayMs": 15
  },
  {
    "Name": "image click2",
    "DelayMs": 125
  }
]

it doesn't matter what is in the json, it will be automatically cut to data that is in AutoEvent class since you are using it for deserialization.

In order to get all information, you have to use the highest inheritance level to deserialize, not the lowest one. This code was tested in VS and working properly.

List<ClickImageEvent> events1 = ser.Deserialize<List<ClickImageEvent>>(json);
 MouseClickEvent event1 = (MouseClickEvent)events[0]; 
 ClickImageEvent event2 = (ClickImageEvent)events[2];

and all classes should inherit from each other

public class ClickImageEvent : MouseClickEvent
{
    public List<string> ImgFiles { get; set; }
    public Rectangle SearchArea { get; set; }
    public double ImgTolerance { get; set; }
    public double ImgError { get; set; }
    public ClickImageEvent(string name, int delayMs, Rectangle searchArea, bool isDoubleClick, double imgTol=0, double imgErr=0) : base(name, delayMs,searchArea, isDoubleClick)
    {
        SearchArea = searchArea;
        IsDoubleClick = isDoubleClick;
        ImgTolerance = imgTol;
        ImgError = imgErr;
    }
    public ClickImageEvent() { }

}

or if you want to derive from the AutoEvent only, you can user mapper or convert manually or using linq Select

 var ev =events[0]; 

MouseClickEvent event1 = new MouseClickEvent( ev.Name, ev.DelayMs, ev.SeachArea,...);

if you need to convert using linq for example, you will have to add type of the class

public class AutoEvent
 {
  public string TypeName { get; set; } = "Auto";
.....
}

that should be assign when you create the object, before serialiazation.

Serge
  • 40,935
  • 4
  • 18
  • 45
  • I could put all members in AutoEvent and not use derived classes at all, it is similar as you suggested, but i asked how can i do it with a list of derived objects where i have base class and derived as i explained in the question. – Nenad Birešev Nov 30 '21 at 16:34
  • Actually serializing List also members of derived classes get serialized because at run time those objects are ClickImageEvent and MouseClickEvent. Serialization is fine, all members of derived classes get serialized, the issue is deserilaization, that it only deserializes the members of AutoEvent base class, it doesn't conclude that the objects are of type ClickImageEvent and MouseClickEvent in the json file. – Nenad Birešev Nov 30 '21 at 16:36
  • @NenadBirešev See my second part of my answer. I told you that you will need to convert manually or using linq or mapper – Serge Nov 30 '21 at 16:37
  • "or if you want to derive from the AutoEvent only, you can user mapper or convert manually or using linq Select" the mapper or linq dosn't help as the information about members of derived objects is lost after deserialization. – Nenad Birešev Nov 30 '21 at 16:39
  • Yes, you are right . You will have to add a string property for a type – Serge Nov 30 '21 at 16:41
  • Still there is the same problem when deserializing the members of derived class are not deserialized so it doesn't help any kind of mapper or converting using string TypeName if information is lost after deserialize. – Nenad Birešev Nov 30 '21 at 18:09
  • @NenadBirešev I am sory I am not a magician. If it would be the way you want , all software developers would have lost their job. – Serge Nov 30 '21 at 18:14