1

Small intro to explain the context:

On Server side:

  • I have an assembly "TheoreticalObjects"
  • this assembly contains a base class "BaseClass" from which all my THEORETICAL objects are derivating
  • the classes inheriting from TheoreticalObjects.BaseClass are for example: TheoreticalObjects.Tube, TheoreticalObjects.Flange, TheoreticalObjects.Caps...

On Client side:

  • I have also that assembly for theoretical objects
  • but I have an other assembly (dedicated to generate vertices) called "RealObjects"
  • this assembly contains a base class "BaseClass" from which all my REAL objects are derivating
  • the classes inheriting from RealObjects.BaseClass are for example: RealObjects.Tube, RealObjects.Flange, RealObjects.Caps...

I want to serialize my objects on the server (as TheoreticalObjects.BaseClass), send the json by tcp to the client, and deserialize the json (as RealObjects.BaseClass).

Here are my classes: (Let's suppose that we want to create a Tube):

// my TheoreticalObjects.BaseClass
namespace TheoreticalObjects
{
    [DataContract]
    public class BaseClass
    {
        [DataMember]
        public Guid GUID { get; set; }

        [DataMember]
        public int ID { get; set; }

        [DataMember]
        public string Designation { get; set; }

        [DataMember]
        public string Product { get; set; }

        [DataMember]
        public int IDMaterial { get; set; }

        [DataMember]
        public int Quantity { get; set; }

        [DataMember]
        public string Form { get; set; }

        protected BaseClass()
        { }
        protected BaseClass(int iD, string designation, string product, int iDMaterial, int quantity, string form) 
        {
            ID = iD;
            Designation = designation;
            Product = product;
            IDMaterial = iDMaterial;
            Quantity = quantity;
            Form = form;
        }
    }
}
// my TheoreticalObjects.Tube
namespace TheoreticalObjects
{
    [DataContract]
    public class Tube : BaseClass
    {
        [DataMember]
        public Length Diameter { get; set; }

        [DataMember]
        public Length WallThickness { get; set; }

        [DataMember]
        public Length Length { get; set; }

        public Tube() : base()
        { }

        public Tube(int iD, string designation, string product, int iDmaterial, int quantity, string form, Length diameter, Length Wallthickness, Length length)  : base(iD, designation, product, iDmaterial, quantity,form)
        {
            WallThickness = Wallthickness;
            Diameter = diameter;
            Length = length;
        }

    }
}
// my RealObjects.BaseClass
namespace RealObjects
{
    public class BaseClass
    {

        public Guid GUID { get; set; }
        public int ID { get; set; }
        public string Designation { get; set; }
        public string Product { get; set; }
        public int IDMaterial { get; set; }
        public int Quantity { get; set; }
        public string Form { get; set; }


        protected BaseClass() { }
        protected BaseClass(int iD, string designation, string product, int iDMaterial, int quantity, string form)
        {
            ID = iD;
            Designation = designation;
            Product = product;
            IDMaterial = iDMaterial;
            Quantity = quantity;
            Form = form;
        }


        public List<Face> myFaces = new List<Face>(); // faces of the mesh
        public MyMesh mesh = new MyMesh();

        public void Triangulation(TopoDS_Shape shape, double deflection)
        {
            // things ...


        myFaces = things...
            mesh = new MyMesh(myFaces);
        }


    }
}
// my RealObjects.Tube
namespace RealObjects
{
    public class Tube: BaseClass
    {

        public double diameter;
        public double Wallthickness;
        public double length;

        public Tube() : base() { }
        public Tube(int iD, string designation, string product, int iDmaterial, int quantity, string form, double diameter, double wallThickness, double length) : base(iD, designation, product, iDmaterial, quantity, form)
        {
            this.diameter = diameter;
            this.Wallthickness = wallThickness;
            this.length = length;

            Build(diameter, Wallthickness, length);
        }

        public void Build(double diameter, double Wallthickness, double length)
        {
           //things ...


           Triangulation(things...);
        }


    }
}

My problem is that after serializing and sending my Tube to the client, it doesn't deserialize correctly: I get a RealObjects.BaseClass instead of a RealObjects.BaseClass.Tube.

  • I did a Binder to bind names to types, but BindToType() isn't getting called at all when deserializing

_______________On server side____________

//creating the Tube  
TheoreticalObjects.Tube c = new TheoreticalObjects.Tube(1, "Tube", "Element", 1, 1, "tube", new Length(1, UnitsNet.Units.LengthUnit.Meter), new Length(0.1, UnitsNet.Units.LengthUnit.Meter), new Length(2, UnitsNet.Units.LengthUnit.Meter));
// settings for the serializer
JsonSerializerSettings _jsonSerializerSettingsOCCServer = new JsonSerializerSettings { Formatting = Newtonsoft.Json.Formatting.Indented };
_jsonSerializerSettingsOCCServer.Converters.Add(new UnitsNetJsonConverter());
// serialization
string json = JsonConvert.SerializeObject(c, _jsonSerializerSettingsOCCServer).Replace("\r\n", "\n");
// the message that the server will send
CommunicateElement messageObject = new CommunicateElement(NetworkComms.NetworkIdentifier, json, 1234, c.Designation);

after that the message is sent

_______________On client side____________

the message is handled, a function put the message in a "constructionQueue"

// settings for the deserializer
_jsonSerializerSettingsOCC = new JsonSerializerSettings {
            TypeNameHandling = TypeNameHandling.All,
            Binder = new MyBinder(),
            NullValueHandling = NullValueHandling.Ignore,
            DefaultValueHandling = DefaultValueHandling.Ignore,
            Formatting = Newtonsoft.Json.Formatting.Indented,
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        };
_jsonSerializerSettingsOCC.Converters.Add(new UnitsNetJsonConverter());
// deserialize the json (that was previously a TheoreticalObjects.Tube) into a RealObjects.BaseClass and add it to the construction queue
constructionQueue.Add(JsonConvert.DeserializeObject<RealObjects.BaseClass>(messageObject.Message, _jsonSerializerSettingsOCC));

...... ...... once it is in the construction queue, I try creating it ..... ...... no need to know what happens after that ..... ...... note that i'm looking for a RealObjects.Tube and not a RealObjects.BaseClass ..... ......

_______________ MyBinder ____________

public class MyBinder : SerializationBinder
    {
        readonly Dictionary<Type, string> typeToName = new Dictionary<Type, string>();
        readonly Dictionary<string, Type> nameToType = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);

        public MyBinder()
        {
            List<Type> myTypes = new List<Type>();
            Assembly[] myAssemblies = AppDomain.CurrentDomain.GetAssemblies();
            for (int i = 0; i < myAssemblies.Length; i++)
            {
                if (myAssemblies[i].GetName().Name == "RealObjects")
                {
                    foreach (Type t in myAssemblies[i].GetTypes())
                    {
                        if (t.IsSubclassOf(typeof(RealObjects.BaseClass)))
                        {
                            myTypes.Add(t);
                        }
                    }
                    break;
                }
            }

            foreach (var type in myTypes)
            {
                Map(type, type.Name);
            }
        }

        public void Map(Type type, string name)
        {
            this.typeToName.Add(type, name);
            this.nameToType.Add(name, type);
        }


        public Type Get(string typeName)
        {
            return nameToType[typeName];
        }

        public string Get(Type type)
        {
            return typeToName[type];
        }

        public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
        {
            // we retrieve the name in the RealObjects assembly
            typeName = Get(serializedType);
            assemblyName = "RealObjects";
        }

        public override Type BindToType(string assemblyName, string typeName)
        {
            return Get(typeName);
        }
    } // credit: https://stackoverflow.com/questions/11099466/using-a-custom-type-discriminator-to-tell-json-net-which-type-of-a-class-hierarc

I wasn't able to call the constructor because my RealObjects.BaseClass doesn't have the fields Diameter, wallThickness and Length that my RealObjects.Tube does have, and I loose their values when deserializing to RealObjects.BaseClass

// called after being added to the construction queue
private void CreateObject(RealObjects.BaseClass c)
    {
        MyBinder binder = new MyBinder();
        Type type = binder.BindToType("RealObjects", c.Designation); 
        ConstructorInfo[] ctor = type.GetConstructors();
        BasicClass be;
        foreach (ConstructorInfo ci in ctor)
        {
            try
            {
                object instance = ci.Invoke(new object[] { c });
                be = (BasicClass )instance;
            } catch (Exception e)
            {
                Debug.Log(e.ToString());
            }

        }

        // things...

    }

All suggestions are open

I hope my english wasn't too bad, and that I explained myself clearly, thank you for any help

Beefr
  • 59
  • 6
  • I think that the problem comes mainly from the fact that the binder isn't called when it should dynamically deserialize the json into a RealObjects.Tube, instead I get a RealObjects.BaseClass in which diameter, length and wallTichkness are not present (that's why I can't convert it into a Tube later) – Beefr Aug 01 '19 at 07:30
  • 1
    Your code doesn't compile (no definition for `Length` or `UnitsNetJsonConverter`) so there's no [mcve], but it seems you didn't output polymorphic type information for the *root object* on the server side when serializing. To do that see [Serializing an interface/abstract object using NewtonSoft.JSON](https://stackoverflow.com/q/28128923/3744182). But do be aware of the security that are mentioned in the accepted answer. – dbc Aug 01 '19 at 09:40
  • 1
    Rough demo of the the linked answer: https://dotnetfiddle.net/SgBcWK. If you want to ensure that $type appears only on the root, see [json.net - how to add property $type ONLY on root object](https://stackoverflow.com/q/36356336/3744182). – dbc Aug 01 '19 at 10:24
  • Length and UnitsNetJsonConverter are classes from resp. UnitsNet.dll and UnitsNet.Serialization.JsonNet.dll. – Beefr Aug 01 '19 at 12:22

1 Answers1

1

The missing part of the puzzle was the way of serializing that was missing the "polymorphic type information for the root object", which needed to be added as shown in this answer to Serializing an interface/abstract object using NewtonSoft.JSON by dbc:

TheoreticalObjects.Tube c = new TheoreticalObjects.Tube(1, "Tube", "Element", 1, 1, "tube", new Length(1, UnitsNet.Units.LengthUnit.Meter), new Length(0.1, UnitsNet.Units.LengthUnit.Meter), new Length(2, UnitsNet.Units.LengthUnit.Meter));
JsonSerializerSettings _jsonSerializerSettingsOCCServer = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, Formatting = Newtonsoft.Json.Formatting.Indented };
_jsonSerializerSettingsOCCServer.Converters.Add(new UnitsNetJsonConverter());
string json = JsonConvert.SerializeObject(c, typeof(TheoreticalObjects.BaseClass), _jsonSerializerSettingsOCCServer);

The settings for the deserialization part remains the same as on my first post. But the constructor of the binder changes:

public MyBinder()
{
    List<Type> myTypes = new List<Type>();
    Assembly[] myAssemblies = AppDomain.CurrentDomain.GetAssemblies();
    for (int i = 0; i < myAssemblies.Length; i++)
    {
        if (myAssemblies[i].GetName().Name == "RealObjects")
        {
            foreach (Type t in myAssemblies[i].GetTypes())
            {
                if (t.IsSubclassOf(typeof(RealObjects.BaseClass)))
                {
                    myTypes.Add(t);
                }
            }
            break;
        }
    }

    foreach (var type in myTypes)
    {
        Map(type, "TheoreticalObjects."+type.Name); //this part changed
    }
}

that way you bound to the right class (RealObjects.Tube)

I was having an instance of the class RealObjects.Tube in my construction queue

Note that I had to modify the fields in my RealObjects.Tube to be of Length type and not double type

dbc
  • 104,963
  • 20
  • 228
  • 340
Beefr
  • 59
  • 6