16

I just ran into an error when I was using the Newtonsoft.Json SerializeObject method. It has been asked before here, but there was no answer from the people working with Newtonsoft as to why this happens.

Basically, when calling SerializeObject like this:

string json = Newtonsoft.Json.JsonConvert.SerializeObject(from, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });

I get errors in a lot of Equals methods I have overridden in my classes:

public override bool Equals(object obj)
{
    if (obj == null)
        return false;

    CapacityConfiguration cc = (CapacityConfiguration)obj; // <-- TypeCastException here; other Properties of the same class are sent in as parameter!
}

And of course I realize that it's "easy" to fix, by checking like this:

public override bool Equals(object obj)
{
    if (obj is CapacityConfiguration == false)
        return false;

    CapacityConfiguration cc = (CapacityConfiguration)obj;
}

But the real question is: Why does Json.Net send in other types of objects in the Equals method of the class? More specifically, Json.Net seems to send in a lot of other properties in the class, instead of another object of the same type.

To me, it's completely weird. Any input would be appreciated.

I am using "Version 8.0.0.0" according to Visual Studio.

UPDATE 1

It's easy to test, as it is reproducible:

public class JsonTestClass
{
    public string Name { get; set; }
    public List<int> MyIntList { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        JsonTestClass jtc = (JsonTestClass)obj;
        return true;
    }
}

And then just place this code in Program.cs or anywhere else:

JsonTestClass c = new JsonTestClass();
c.Name = "test";
c.MyIntList = new List<int>();
c.MyIntList.Add(1);

string json = Newtonsoft.Json.JsonConvert.SerializeObject(c, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });

and you will get the TypeCast Exception:

enter image description here

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
Ted
  • 19,727
  • 35
  • 96
  • 154
  • 4
    Any chance you can provide a [mcve]? – Lasse V. Karlsen Aug 03 '18 at 09:08
  • 1
    Is this just to detect possible cycles in object graphs? – Wiktor Zychla Aug 03 '18 at 09:11
  • @LasseVågsætherKarlsen Well, I dont have that ready, no. I was running this when I was convertnig some data in my large project, so I havent made a test-project. I thought maybe it could be something well-known that someone could explain, but I will trt to put together a test and see if I can replicate it there. – Ted Aug 03 '18 at 09:11
  • 2
    In any case, the likely reason is probably that json.net is seeing if you're serializing cyclic objects (objects which (in)directly refer to themselves). Your `Equals(object)` method should be prepared to handle any type anyway so regardless of json.net, that is the way to do it. – Lasse V. Karlsen Aug 03 '18 at 09:14
  • I have updated my question with "UPDATE 1" above, easy to reproduce. @LasseVågsætherKarlsen Is that a guess or do you know that is a fact? Cause my initial thought was that there is a serious error...? – Ted Aug 03 '18 at 09:21
  • @Ted - if you take a look at [Implementing the Equals Method](https://msdn.microsoft.com/en-us/library/336aedhh.aspx) you will see that the overridden `Equals()` methods always check to see whether the incoming object is of the same type as the current object, e.g. *The Point.Equals method checks that the obj argument is not null and that it references an instance of the **same type** as this object.* – dbc Aug 03 '18 at 10:28
  • _"Cause my initial thought was that there is a serious error...?"_ - there is, your `Equals()` method is broken. – CodeCaster Aug 03 '18 at 10:30
  • hehe, right, I meant with Json.Net, but I guess you got that ;-) – Ted Aug 03 '18 at 10:34
  • Note - if you would prefer reference equality to be used, see the workaround mentioned in the answer to [Why doesn't reference loop detection use reference equality?](https://stackoverflow.com/q/46936395/3744182). – dbc Dec 03 '18 at 20:52

1 Answers1

19

Why will JsonConvert.SerializeObject call the object.Equals method?

Because when you use JsonConvert.SerializeObject, there is a method CheckForCircularReference which is called to check whether a property re-references your own object, leading to an infinite loop.

 private bool CheckForCircularReference(JsonWriter writer, object value, JsonProperty property, JsonContract contract, JsonContainerContract containerContract, JsonProperty containerProperty)

In the CheckForCircularReference method, part of code uses the Contains method, which will call object.Equals if your object didn't implement the IEquatable<T> interface.

bool exists = (Serializer._equalityComparer != null)
                ? _serializeStack.Contains(value, Serializer._equalityComparer)
                : _serializeStack.Contains(value);

Explanation

  1. _serializeStack is a list of objects currently being serialized.
  2. The List<T>.Contains method checks whether the current property is or isn't contained in the collection.
  3. List<T>.Contains uses EqualityComparer<T>.Default, which in turn uses IEquatable<T> if the type implements it, or object.Equals otherwise.
  4. The object value parameter is your current Property object.

Here is an example of a self-referencing loop:

public class JsonTestClass
{
    public string Name { get; set; }
    public List<int> MyIntList { get; set; }
    public JsonTestClass Test{get;set;}
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        JsonTestClass jtc = (JsonTestClass)obj;
        return true;
   }
}

JsonTestClass c = new JsonTestClass();
c.Name = "test";
c.Test = c;
string json = JsonConvert.SerializeObject
               (c, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });

We will get an exception:

Self referencing loop detected for property 'test' with type 'Program+JsonTestClass'. Path ''.

But if we do it like this there is no error:

JsonTestClass c = new JsonTestClass();
c.Name = "test";
c.Test = new JsonTestClass();

string json = JsonConvert.SerializeObject
       (c, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
D-Shih
  • 44,943
  • 6
  • 31
  • 51