4

I am using JSON.NET 6.0.3. I have changed PreserveReferences option as follows:

HttpConfiguration.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;

My object graph resembles the following:

public class CarFromManufacturer
{
    public int CarID { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
    public CarManufacturer Manufacturer { get; set; }
}

public class CarManufacturer
{
    public int ManufacturerID { get; set; }
    public string Name { get; set; }
}

My WebAPI controller is returning the result set of IEnumerable[CarFromManufacturer]. So the result could be a list of 5 cars from two unique manufacturer objects. I am expecting the JSON result to list each manufacturer only once fully serialized and then subsequent uses of the same Manufacturer to be $ref ID to the original's $id. That is not happening.

Even though I can't find a single piece of documentation that speaks about how equality is established for the ReferenceResolver, I've implemented IEquatable<CarManufacturer> along with override of base.Equals and base.GetHashCode() with no luck.

I'd like to avoid implementing my own IReferenceResolver because have very similar object graphs working as expected in the same project.

The only thing I can think of is that I am using factory objects and instead of creating each unique CarManufacturer first, then creating the instances of CarFromManufacturer passing in CarManufacturer... i am creating a new instance of the CarManufacturer. This would explain why the objects aren't equal, but that's why I implemented IEquatable and overrides of base.Equals(object) and base.GetHashCode().

I've looked into the source for DefaultReferenceResolver and it uses the default constructor of BidirectionalDictionary which uses EqualityComparer<T>.Default which, from MSDN documentation, uses the T's implementation of IEquatable<T> if it exists, or otherwise uses T's base.Equals() implementation.... all of this would lead me to believe that IEquatable in CarManufacturer should fix my problem. However, placing breakpoints in CarManufacturer.Equals() and GethashCode() never hit..

Miamy
  • 2,162
  • 3
  • 15
  • 32
diegohb
  • 1,857
  • 2
  • 16
  • 34
  • The reason why I am creating a new instance of Manufacturer each time is because my factory object takes in an EF entity of a join table record (joins car and manufacturer).. so it builds the car from the EF Car object and calls onto a ManufacturerFactory with the EF Manufacturer. – diegohb Aug 29 '14 at 11:59
  • Looks like the object references are compared for equality. What's wrong with implementing an `IReferenceResolver`? – Andrew Whitaker Aug 29 '14 at 13:40
  • Thanks for the thought! Does overriding Equals and GetHashCode not suffice to let JSON.NET's DefaultReferenceResolver know how to compare objects? Nothing is wrong with implementing IReferenceResolver except that I wouldn't need that extra complexity if it weren't for this problem.. I'd rather restructure the factory code before doing that. But the question is really about how the DefaultReferenceResolver compares equality. I'm updating the post.. – diegohb Aug 29 '14 at 16:21
  • Looking through the source, I believe it uses [this comparer](https://github.com/JamesNK/Newtonsoft.Json/blob/ee170dc5510bb3ffd35fc1b0d986f34e33c51ab9/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs#L37) by default, which only compares references – Andrew Whitaker Aug 29 '14 at 16:25
  • *Concerning your update*: `DefaultReferenceResolver` calls [`JsonSerializerInternalBase.DefaultReferenceMappings`](https://github.com/JamesNK/Newtonsoft.Json/blob/ee170dc5510bb3ffd35fc1b0d986f34e33c51ab9/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs#L74). This method uses the comparer I mentioned above and does not use the default constructor for `BidirectionalDictionary`. – Andrew Whitaker Aug 29 '14 at 16:40
  • You're right @AndrewWhitaker - I read the code wrong.. That does explain it - the objects are not the same reference and I confirmed EqualityComparer.Default.Equals() does call my Equals() implementation and return true. Next question is, in the context of WebAPIs, model classes (projections of entities from db) intended for read/queries, is it bad practice to ignore reference equality.. i believe overriding the == and !== operator would allow me to do that. not sure I should.. thoughts? (ps - please post an answer so I can mark as an answer) – diegohb Aug 29 '14 at 16:49
  • I would not override the `==` and `!=` operators, only because it's not easy to tell that those operators *have* been overridden when you're reading through code. JSON.NET aside, I'd use a separate `EqualityComparer` (like you've done) to compare the instances. – Andrew Whitaker Aug 29 '14 at 17:02

2 Answers2

3

JSON.NET's logic for resolving references by default just compares references using this comparer.

If you want to compare objects in a different manner, you'll have to implement a custom IReferenceResolver.

Here's an example that takes an IEqualityComparer<T> to accommodate your use case:

public class ReferenceResolver<T> : IReferenceResolver
{
    private Dictionary<string, T> stringToReference;
    private Dictionary<T, string> referenceToString;

    private int referenceCount;

    public ReferenceResolver(IEqualityComparer<T> comparer)
    {
        this.stringToReference = new Dictionary<string, T>();
        this.referenceToString = new Dictionary<T, string>(comparer);
        this.referenceCount = 0;
    }

    public void AddReference(
        object context,
        string reference,
        object value)
    {
        this.referenceToString.Add((T)value, reference);
        this.stringToReference.Add(reference, (T)value);
    }

    public string GetReference(
        object context,
        object value)
    {
        string result = null;

        if (!this.referenceToString.TryGetValue((T)value, out result))
        {
            referenceCount++;
            result = referenceCount.ToString(CultureInfo.InvariantCulture);

            this.referenceToString.Add((T)value, result);
            this.stringToReference.Add(result, (T)value);
        }

        return result;
    }

    public bool IsReferenced(
        object context,
        object value)
    {
        return this.referenceToString.ContainsKey((T)value);
    }

    public object ResolveReference(
        object context,
        string reference)
    {
        T r = default(T);

        this.stringToReference.TryGetValue(reference, out r);
        return r;
    }
}
Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
  • Thanks a lot! Ideally, the resolver can be applied globally at HttpConfiguration.Formatters.JsonFormatter.SerializerSettings.ReferenceResolver and makes use of dependency resolution. I'm going to boldly assume all model objects that will be serialized are immutable so therefore ReferenceEquality is not important - for purposes of serialization. thanks! – diegohb Aug 29 '14 at 17:59
  • In case you have any insight... https://github.com/JamesNK/Newtonsoft.Json/issues/411 – diegohb Nov 02 '14 at 17:38
1

Json.Net will call the Equals method on the objects being compared. In certain scenarios you may not want this however for example when it is checking for circular references it does the same whereas it may be more ideal to check for reference equality. They do this however to give the developer full control by overridding the Equals method in their classes.

You can override the default implementation. For example to make this a reference equality you would do the following:

var settings = new JsonSerializerSettings
                           {
                               EqualityComparer = new DefaultEqualityComparer(),
                           };

public class DefaultEqualityComparer : IEqualityComparer
    {
        public bool Equals(object x, object y)
        {
            return ReferenceEquals(x, y);
        }

        public int GetHashCode(object obj)
        {
            return obj.GetHashCode();
        }
    }
Thulani Chivandikwa
  • 3,402
  • 30
  • 33