3

I have the same class in two projects one is being sent on runtime to the other process that has to de-serialize that object and use it (giving that the two objects are the same but differ in assembly name so they are actually interpreted as two different types). From my research i have came with those solutions that doesn't work for the following reasons.

Json.NET: giving me an exceptions that the two types aren't compatible (tried using typename.all in serialization settings).

protobuf-net: requires me to add attributes every where or simply provide it with the properties name (in v2), which both aren't possible for me due to the fact that my object is too complex.

BinaryFormatter: same reasons as protobuf-> tons of attributes.

Use Common Assembly: For some reasons related to the architecture of my project i can't.

So is there any simple way to serialize one type then de-serializing it to another type (which is virtually the same class but in different assembly) ?

Daniel Eugen
  • 2,712
  • 8
  • 33
  • 56
  • Could/do both assemblies reference a *common* assembly? – BradleyDotNET Feb 13 '15 at 22:11
  • @BradleyDotNET, I Can't because for some reasons. – Daniel Eugen Feb 13 '15 at 22:18
  • 2
    The only other thing I can think of is to do XML without any of the namespaces. The best solution by far is to use a common library though. – BradleyDotNET Feb 13 '15 at 22:21
  • You could always build a JSON template file that is shared by both assemblies. It would be a bit lighter than XML and would flow nicely with Json.NET – David L Feb 13 '15 at 22:22
  • @DavidL The type i want to share is too complex for me to generate something for it, and i am really sure there is a way around that because the two classes are identical the problem is the difference in assembly name so they can't be casted to each other. – Daniel Eugen Feb 13 '15 at 22:24
  • 1
    @DanielEugen why is the assembly information even getting serialized? The serialized result should be completely transferable to another runtime or language. It shouldn't be concerned about the source assembly metadata. – David L Feb 13 '15 at 22:33
  • @DavidL how, all the libraries or solutions i have mentions concerns about assembly data. I simply get TYPE is not compatible and cannot resolve type errors with every solution i try. Can you provide me with mode details on how could i do this `simply` – Daniel Eugen Feb 13 '15 at 22:43
  • I would go with DTO object and serialize it. If you can express your class as DTO, and restore from it - you can chose any serialization way you like – Alex K. Feb 13 '15 at 22:57
  • @AlexK is there any library that can simplify that process to me, knowing that the object i wanna transfer is too complex that i can't just create a DTO for it, or even mark it with attributes. – Daniel Eugen Feb 13 '15 at 23:13
  • 1
    @DanielEugen, when using JSON.NET try `TypeNameHandling.None` instead of `TypeNameHandling.All`. The `All` value tells the serializer to include type information for all objects, and that type information is what is probably causing the incompatibility error. Setting it to `None` will exclude all type information. You can check for the $type property in the serialized objects to verify if type information is included or not. – Matthew Jaspers Feb 13 '15 at 23:27
  • @BenJaspers When i do this i get cannot create instance of interface `IFace` exception... I AM TRAPPED – Daniel Eugen Feb 13 '15 at 23:39
  • @DanielEugen, yes, everything has to be deserialized to a concrete type. Without knowing what you're trying to serialize/deserialize it's hard to make a recommendation, but if you need to choose among several concrete types I'd [start here](http://stackoverflow.com/questions/8030538/how-to-implement-custom-jsonconverter-in-json-net-to-deserialize-a-list-of-base). – Matthew Jaspers Feb 13 '15 at 23:50
  • @BenJaspers i saw that article while i am searching and it didn't help, what i am trying to do is pretty obvious i have a very complex object that i wanna transfer between two processes that contain the same implementation for that object but they differ in assembly name. i can't post that complex object for some reasons. So how simply can i transfer an object across the assembly boundaries using JSON.Net for instance without getting all the incompatible assembly exceptions ? – Daniel Eugen Feb 14 '15 at 00:08
  • There is no simple way except to reference a common assembly, which you say you can't. – Matthew Jaspers Feb 14 '15 at 00:26
  • 1
    @BenJespers in Json.NET i tried to provide the de-serializer with `TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple` setting which supposed to ignore the assembly name change however i still getting the type specified in JSON is not compatible with exception. – Daniel Eugen Feb 14 '15 at 00:30

1 Answers1

4

Yes, it is absolutely possible to serialize using classes in one assembly and deserialize into classes of another assembly using Json.Net. In fact, that is one of the main use cases for serialization in the first place -- transmitting data between different systems.

There are two things you need to keep in mind:

  1. If your source and target assemblies are different, then you should not include the actual fully-qualified type names as metadata in the JSON. In other words, make sure that the TypeNameHandling setting is set to None (which I believe is the default). If you include the type name metadata, then Json.Net will expect to find those assemblies on the receiving side, and the deserialization will fail because those assemblies are not there.
  2. If you are using interfaces in your class structure instead of concrete types then you will need to create one or more JsonConverter classes to handle the deserialization. When Json.Net sees an interface it will not know what type of concrete class to create, because it could be anything. A converter can look for other data that might be present in the JSON and tell Json.Net which concrete class to instantiate. If there is no obvious piece of data in the JSON that can be used as an indicator for the type, you can use a converter on the serialization side to add a custom indicator.

Here is some example code to demonstrate the concepts I've laid out. The demo is broken into two parts. The first part is the "sender", which serializes a made-up "diagram" class structure to JSON and writes it to a file. The second part is the "receiver", which reads the file and deserializes the JSON into a different set of classes. You'll notice I've intentionally made some of the class names in the receiver different than the sender, but they have the same property names and structure, so it still works. You'll also notice that the receiver program uses a custom JsonConverter to handle creating the correct IFigure instances using the presence of certain properties in the JSON as indicators.

Sender

using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;

namespace Sender
{
    class Program
    {
        static void Main(string[] args)
        {
            Diagram diagram = new Diagram
            {
                Title = "Flowchart",
                Shapes = new List<IShape>
                {
                    new Circle 
                    { 
                        Id = 1, 
                        Text = "Foo", 
                        Center = new Point { X = 1, Y = 5 }, 
                        Radius = 1.25 
                    },
                    new Line 
                    {
                        Id = 2,
                        A = new Point { X = 2.25, Y = 5 }, 
                        B = new Point { X = 4, Y = 5 } 
                    },
                    new Rectangle
                    {
                        Id = 3,
                        Text = "Bar",
                        TopLeft = new Point { X = 4, Y = 6.5 }, 
                        BottomRight = new Point { X = 8.5, Y = 3.5 } 
                    }
                }
            };

            string json = JsonConvert.SerializeObject(diagram, Formatting.Indented);

            File.WriteAllText(@"C:\temp\test.json", json);
        }
    }

    class Diagram
    {
        public string Title { get; set; }
        public List<IShape> Shapes { get; set; }
    }

    interface IShape
    {
        int Id { get; set; }
        string Text { get; set; }
    }

    abstract class AbstractShape : IShape
    {
        public int Id { get; set; }
        public string Text { get; set; }
    }

    class Line : AbstractShape
    {
        public Point A { get; set; }
        public Point B { get; set; }
    }

    class Rectangle : AbstractShape
    {
        public Point TopLeft { get; set; }
        public Point BottomRight { get; set; }
    }

    class Circle : AbstractShape
    {
        public Point Center { get; set; }
        public double Radius { get; set; }
    }

    class Point
    {
        public double X { get; set; }
        public double Y { get; set; }
    }
}

Here is the JSON output file generated by the Sender program:

{
  "Title": "Flowchart",
  "Shapes": [
    {
      "Center": {
        "X": 1.0,
        "Y": 5.0
      },
      "Radius": 1.25,
      "Id": 1,
      "Text": "Foo"
    },
    {
      "A": {
        "X": 2.25,
        "Y": 5.0
      },
      "B": {
        "X": 4.0,
        "Y": 5.0
      },
      "Id": 2,
      "Text": null
    },
    {
      "TopLeft": {
        "X": 4.0,
        "Y": 6.5
      },
      "BottomRight": {
        "X": 8.5,
        "Y": 3.5
      },
      "Id": 3,
      "Text": "Bar"
    }
  ]
}

Receiver

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Receiver
{
    class Program
    {
        static void Main(string[] args)
        {
            string json = File.ReadAllText(@"C:\temp\test.json");

            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.Converters.Add(new FigureConverter());

            Chart chart = JsonConvert.DeserializeObject<Chart>(json, settings);

            Console.WriteLine(chart);
            Console.ReadKey();
        }
    }

    class FigureConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(IFigure));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject jo = JObject.Load(reader);
            if (jo["Center"] != null)
            {
                return jo.ToObject<Circle>(serializer);
            }
            else if (jo["TopLeft"] != null)
            {
                return jo.ToObject<Rectangle>(serializer);
            }
            else
            {
                return jo.ToObject<Line>(serializer);
            }
        }

        public override bool CanWrite
        {
            get { return false; }
        }

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

    class Chart
    {
        public string Title { get; set; }
        public List<IFigure> Shapes { get; set; }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("Chart: ");
            sb.AppendLine(Title);
            foreach (IFigure figure in Shapes)
            {
                sb.AppendLine(figure.ToString());
            }
            return sb.ToString();
        }
    }

    interface IFigure
    {
        int Id { get; set; }
        string Text { get; set; }
    }

    abstract class AbstractFigure : IFigure
    {
        public int Id { get; set; }
        public string Text { get; set; }
    }

    class Line : AbstractFigure
    {
        public Point A { get; set; }
        public Point B { get; set; }
        public override string ToString()
        {
            return string.Format("Line: A = {0}, B = {1}", A, B);
        }
    }

    class Rectangle : AbstractFigure
    {
        public Point TopLeft { get; set; }
        public Point BottomRight { get; set; }
        public override string ToString()
        {
            return string.Format("Rectangle: TopLeft = {0}, BottomRight = {1}", TopLeft, BottomRight);
        }
    }

    class Circle : AbstractFigure
    {
        public Point Center { get; set; }
        public double Radius { get; set; }
        public override string ToString()
        {
            return string.Format("Circle: Center = {0}, Radius = {1}", Center, Radius);
        }
    }

    class Point
    {
        public double X { get; set; }
        public double Y { get; set; }
        public override string ToString()
        {
            return string.Format("({0:0.##}, {1:0.##})", X, Y);
        }
    }
}

Here is the output of the Receiver program:

Chart: Flowchart
Circle: Center = (1, 5), Radius = 1.25
Line: A = (2.25, 5), B = (4, 5)
Rectangle: TopLeft = (4, 6.5), BottomRight = (8.5, 3.5)
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300