2

While reading on some literature about cloning objects, I came across the phrase

"If your object is out of the box JSON serializable"

Can someone explain this, please?

With some opposite example preferably (a case of a n object which is not out of the box JSON serializable)

cnom
  • 3,071
  • 4
  • 30
  • 60
  • can you please give lilttle bit more context around it? – Mihir Dave Dec 06 '18 at 10:08
  • yes of course @MihirDave, I need to make a generic method to copy c# ojects, and I use code where I use JSON serializing-deserializing, but I found some case where the above is stated, that this solution will only work "If your object is out of the box JSON serializable"... so I try to understand which will be the cases where it does not work... – cnom Dec 06 '18 at 10:13
  • 1
    First thing that comes to mind is having circular references in your object. If you have them, depending on the serializer you use you might or might not be able to ignore them. – Snak Dec 06 '18 at 10:13
  • than u @Snak! if you provide such an example, I will give you the answer – cnom Dec 06 '18 at 10:17
  • "but I found some case" can you share such cases? – Mihir Dave Dec 06 '18 at 10:24
  • @MihirDave here is such a link: https://stackoverflow.com/a/15788750/4295234 – cnom Dec 06 '18 at 10:37

3 Answers3

3

Try to serialize an object with TcpClient property using Newtonsoft.Json:

using Newtonsoft.Json;
using System.Net.Sockets;

class A
{ 
    public TcpClient TcpClient { get; set; } = new TcpClient();
}

var a = new A();

JsonConvert.SerializeObject(a);

It will throw following exception:

Error getting value from 'MulticastLoopback' on 'System.Net.Sockets.Socket'.

Accessing MulticastLoopback from TcpClient throws an exception:

var ml = a.TcpClient.Client.MulticastLoopback;

but if we would have UdpClient instead:

class A
{ 
    public UdpClient UdpClient { get; set; } = new UdpClient();
}

var a = new A();
var ml = a.UdpClient.Client.MulticastLoopback; // ok here

JsonConvert.SerializeObject(a); // Exception here

Then accessing that particular property will not fail, but it will fail at

var ls = a.UdpClient.Client.LingerState;

And serialization will throw an exception:

Error getting value from 'LingerState' on 'System.Net.Sockets.Socket'.

UdpClient and TcpClient share common property ".Client" which is of a type System.Net.Sockets.Socket and some members in this class are valid only for TCP connections and some only for UDP connections.

MulticastLoopback cannot be accessed from TCP sockets and LingerState cannot be accessed from UDP sockets, thus TcpClient and UdpClient are not "out of the box JSON serializable".

EDIT: To summarize, serialization for some types of objects doesn't make sense, so they are not designed to be serializable. Socket class is the one.

  • Impresive case! So, @BART, these two examples you gave are cases where some properties of the objects are not accessible and throw exception upon get? Am I correct? I suspect this can be categorized as extreme corner cases, though. – cnom Dec 06 '18 at 11:20
  • @cnom Yes that's correct. The main issue here is that the implementation of System.Net.Sockets.Socket class wasn't designed to be serializable . You cannot serialize it and then deserialize at the other end to get exact same object. To summarize, serialization for some types of objects doesn't make sense, so they are not designed to be serializable. Socket class is the one. –  Dec 06 '18 at 13:44
2

What can and cannot be serialized depends on the implementation, but let's take a look at the documentation for Json.NET which is the most commonly used JSON serialization library:

At a high level, the Json.NET serializer will convert primitive .NET values into primitive JSON values, will convert .NET arrays and collections to JSON arrays, and will convert everything else to JSON objects.

Json.NET will throw an error if it encounters incorrect JSON when deserializing a value. For example, if the serializer encounters a JSON property with an array of values and the type of matching .NET property is not a collection, then an error will be thrown, and vice-versa.

[...]

By default a type's properties are serialized in opt-out mode. What that means is that all public fields and properties with getters are automatically serialized to JSON, and fields and properties that shouldn't be serialized are opted-out by placing JsonIgnoreAttribute on them. To serialize private members, the JsonPropertyAttribute can be placed on private fields and properties.

[...]

So basically, if the class members are limited to basic .NET types and there are no private members without a matching public member, you should be good to go. If for example you have private int foo without public int Foo { get { return foo; } set { foo = value; } }, data will be lost during serialization (unless you apply JsonPropertyAttribute).

It's worth it to read the full document I linked, so you have a better understanding of how serialization works.

user247702
  • 23,641
  • 15
  • 110
  • 157
  • You gave an example where data may be lost during serialization (i.e. a private member). But does this make the object Not-Serializable? I wouldnt say so. Nevertheless, it is a good point, thank you very much! – cnom Dec 06 '18 at 10:50
  • The other case described in the documentation quoted, is a case where De-Serialization fails, this too seems not to be equal to the object being Not-Serializable. – cnom Dec 06 '18 at 10:53
  • 1
    "not serializable" in the context of cloning objects, means that you lose information when you serialize and deserialize so the cloned object would not be identical to the original object. – user247702 Dec 06 '18 at 11:16
2

First thing that comes to mind are circular dependencies.

Let's say we have a class that has a field referencing its child, and the child class has a field referencing its parent.

public class A
{
    public B Child;
}

public class B
{
    public A Parent;
}

public class Program
{
    private static void Main()
    {
        A a = new A();
        B b = new B();
        a.Child = b;
        b.Parent = a;

        string json = JsonConvert.SerializeObject(a);
    }
}

This would cause a run-time JsonSerializationException at JsonConvert.SerializeObject(a) with the following message:

Self referencing loop detected for property 'Parent' with type 'A'. Path 'Child'.

To avoid this, JSON.NET provides an overload of SerializeObject to pass a settings object, where we can specifiy how to handle circular references.

JsonSerializerSettings settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.None,
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};

JsonConvert.SerializeObject(a, settings);

This way, the output json would completely ignore the circular reference from the child to the parent, and it would look like this:

{
    "Child": {}
}

JSON.NET also provides a way to handle them without losing information. We'd need to specifiy the option PreserveReferencesHandling.Objects in the settings.

JsonSerializerSettings settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects
};

The output JSON can only be interpreted by JSON.NET or other serializers compatible with the $id and $ref syntax, and it will look like this:

{
    "$id":"1",
    "Child": {
        "$id":"2",
        "Parent": {
            "$ref":"1"
        }
    }
}
Snak
  • 673
  • 1
  • 5
  • 19