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..

7
  • 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. Commented Aug 29, 2014 at 11:59
  • Looks like the object references are compared for equality. What's wrong with implementing an IReferenceResolver? Commented Aug 29, 2014 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.. Commented Aug 29, 2014 at 16:21
  • Looking through the source, I believe it uses this comparer by default, which only compares references Commented Aug 29, 2014 at 16:25
  • Concerning your update: DefaultReferenceResolver calls JsonSerializerInternalBase.DefaultReferenceMappings. This method uses the comparer I mentioned above and does not use the default constructor for BidirectionalDictionary. Commented Aug 29, 2014 at 16:40

2 Answers 2

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;
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

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!
In case you have any insight... github.com/JamesNK/Newtonsoft.Json/issues/411
2

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();
        }
    }

1 Comment

This is discussed here as well: github.com/JamesNK/Newtonsoft.Json/issues/401

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.