39

I have an object which has a circular reference to another object. Given the relationship between these objects this is the right design.

To Illustrate

Machine => Customer => Machine

As is expected I run into an issue when I try to use Json to serialize a machine or customer object. What I am unsure of is how to resolve this issue as I don't want to break the relationship between the Machine and Customer objects. What are the options for resolving this issue?

Edit

Presently I am using Json method provided by the Controller base class. So the serialization I am doing is as basic as:

Json(machineForm);
ahsteele
  • 26,243
  • 28
  • 134
  • 248

9 Answers9

58

Update:

Do not try to use NonSerializedAttribute, as the JavaScriptSerializer apparently ignores it.

Instead, use the ScriptIgnoreAttribute in System.Web.Script.Serialization.

public class Machine
{
    public string Customer { get; set; }

    // Other members
    // ...
}

public class Customer
{
    [ScriptIgnore]
    public Machine Machine { get; set; }    // Parent reference?

    // Other members
    // ...
}

This way, when you toss a Machine into the Json method, it will traverse the relationship from Machine to Customer but will not try to go back from Customer to Machine.

The relationship is still there for your code to do as it pleases with, but the JavaScriptSerializer (used by the Json method) will ignore it.

David Neale
  • 16,498
  • 6
  • 59
  • 85
Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • I will have to give that a shot, but unfortunately this is coming from a domain/core object and I am not sure I want to introduce a reference to System.Web.Script.Serialization to that project. I am encapsulating these core objects in a model which I guess I could use `ScriptIgnore`, but at that point I could just remove the circular reference. This solution seems to provide a similar solution to that with anonymous types http://stackoverflow.com/questions/372955/best-way-to-filter-domain-objects-for-json-output-in-an-asp-net-mvc-application/372977#372977 – ahsteele Jan 05 '10 at 01:46
  • That works too. You should be specific with your question/comments - saying that you can't/won't remove the circular reference is different from saying that you can't/won't change anything at all in the model. I frequently pass in anonymous types to `Json`, the only issue I see here is that if you have more than one method returning a JSON'ed `Machine`, you'll need to remember to "filter" it every time. It's chocolate vs. vanilla in my eyes - they're both fine choices. – Aaronaught Jan 05 '10 at 02:25
  • @Aaron understood. Not sure that I can't / won't just not sure that it *feels* right to add the reference to the core / domain project just to provide this solution for a particular UI situation. I liked the solution using the NonSerializedAttribute as its in the System namespace. It seems like a shortcoming that the JavaScriptSerializer ignores it. – ahsteele Jan 05 '10 at 02:48
  • I do understand where you're coming from. If this is truly specific to a single UI, that suggests a ViewModel, which would be in the UI assembly and thus "OK" to have the serialization reference. Keep in mind, also, that before MVC's great JSON support, we usually used XML, and it was quite common to have a reference to `System.Xml` in an entity library simply to declare `XmlElement`, `XmlIgnore` and similar attributes. But as I said, I understand your rationale, and if you're going to be returning this data frequently then a ViewModel might be the way to go (otherwise anon types are fine). – Aaronaught Jan 05 '10 at 03:02
  • 3
    A final note on utilizing the ScriptIgnoreAttribute from the System.Web.Script.Serialization. Unfortunately it sits in the System.Web.Extensions.dll (http://stackoverflow.com/questions/1156313/adding-system-web-script-reference-in-class-library/1156331#1156331 ) which takes a bit of work to get added: http://forums.asp.net/p/1055356/1500247.aspx. Other than that as discussed it boils down to choice remove the circular reference or add a namespace reference to ignore the serialization of particular properties. – ahsteele Jan 05 '10 at 21:04
33

I'm answering this despite its age because it is the 3rd result (currently) from Google for "json.encode circular reference" and although I don't agree with the answers (completely) above, in that using the ScriptIgnoreAttribute assumes that you won't anywhere in your code want to traverse the relationship in the other direction for some JSON. I don't believe in locking down your model because of one use case.

It did inspire me to use this simple solution.

Since you're working in a View in MVC, you have the Model and you want to simply assign the Model to the ViewData.Model within your controller, go ahead and use a LINQ query within your View to flatten the data nicely removing the offending circular reference for the particular JSON you want like this:

var jsonMachines = from m in machineForm
                   select new { m.X, m.Y, // other Machine properties you desire
                                Customer = new { m.Customer.Id, m.Customer.Name, // other Customer properties you desire
                              }};
return Json(jsonMachines);

Or if the Machine -> Customer relationship is 1..* -> * then try:

var jsonMachines = from m in machineForm
                   select new { m.X, m.Y, // other machine properties you desire
                                Customers = new List<Customer>(
                                               (from c in m.Customers
                                                select new Customer()
                                                {
                                                   Id = c.Id,
                                                   Name = c.Name,
                                                   // Other Customer properties you desire
                                                }).Cast<Customer>())
                               };
return Json(jsonMachines);
eudaimos
  • 665
  • 7
  • 11
10

Based on txl's answer you have to disable lazy loading and proxy creation and you can use the normal methods to get your data.

Example:

//Retrieve Items with Json:
public JsonResult Search(string id = "")
{
    db.Configuration.LazyLoadingEnabled = false;
    db.Configuration.ProxyCreationEnabled = false;

    var res = db.Table.Where(a => a.Name.Contains(id)).Take(8);

    return Json(res, JsonRequestBehavior.AllowGet);
}
Thomas
  • 2,127
  • 1
  • 32
  • 45
  • Thanks - this is my favorite solution in this question because it prevents unnecessary framework computation rather than using more code to "back out" into a subset of data after the unnecessary work executes. – Stephen May 28 '15 at 19:33
  • HFS it took me 3 days to find this, but thank you so much for posting this solution!!!! – user3320597 May 31 '16 at 14:38
4

Use to have the same problem. I have created a simple extension method, that "flattens" L2E objects into an IDictionary. An IDictionary is serialized correctly by the JavaScriptSerializer. The resulting Json is the same as directly serializing the object.

Since I limit the level of serialization, circular references are avoided. It also will not include 1->n linked tables (Entitysets).

    private static IDictionary<string, object> JsonFlatten(object data, int maxLevel, int currLevel) {
        var result = new Dictionary<string, object>();
        var myType = data.GetType();
        var myAssembly = myType.Assembly;
        var props = myType.GetProperties();
        foreach (var prop in props) {
            // Remove EntityKey etc.
            if (prop.Name.StartsWith("Entity")) {
                continue;
            }
            if (prop.Name.EndsWith("Reference")) {
                continue;
            }
            // Do not include lookups to linked tables
            Type typeOfProp = prop.PropertyType;
            if (typeOfProp.Name.StartsWith("EntityCollection")) {
                continue;
            }
            // If the type is from my assembly == custom type
            // include it, but flattened
            if (typeOfProp.Assembly == myAssembly) {
                if (currLevel < maxLevel) {
                    result.Add(prop.Name, JsonFlatten(prop.GetValue(data, null), maxLevel, currLevel + 1));
                }
            } else {
                result.Add(prop.Name, prop.GetValue(data, null));
            }
        }

        return result;
    }
    public static IDictionary<string, object> JsonFlatten(this Controller controller, object data, int maxLevel = 2) {
        return JsonFlatten(data, maxLevel, 1);
    }

My Action method looks like this:

    public JsonResult AsJson(int id) {
        var data = Find(id);
        var result = this.JsonFlatten(data);
        return Json(result, JsonRequestBehavior.AllowGet);
    }
GvS
  • 52,015
  • 16
  • 101
  • 139
2

In the Entity Framework version 4, there is an option available: ObjectContextOptions.LazyLoadingEnabled

Setting it to false should avoid the 'circular reference' issue. However, you will have to explicitly load the navigation properties that you want to include.

see: http://msdn.microsoft.com/en-us/library/bb896272.aspx

ahsteele
  • 26,243
  • 28
  • 134
  • 248
txl
  • 21
  • 1
1

Since, to my knowledge, you cannot serialize object references, but only copies you could try employing a bit of a dirty hack that goes something like this:

  1. Customer should serialize its Machine reference as the machine's id
  2. When you deserialize the json code you can then run a simple function on top of it that transforms those id's into proper references.
Swizec Teller
  • 2,322
  • 1
  • 19
  • 24
  • Sure but that doesn't deliver the machine information to the querying page and would require additional queries to get the Machine properties. – ahsteele Jan 04 '10 at 23:36
0

You need to decide which is the "root" object. Say the machine is the root, then the customer is a sub-object of machine. When you serialise machine, it will serialise the customer as a sub-object in the JSON, and when the customer is serialised, it will NOT serialise it's back-reference to the machine. When your code deserialises the machine, it will deserialise the machine's customer sub-object and reinstate the back-reference from the customer to the machine.

Most serialisation libraries provide some kind of hook to modify how deserialisation is performed for each class. You'd need to use that hook to modify deserialisation for the machine class to reinstate the backreference in the machine's customer. Exactly what that hook is depends on the JSON library you are using.

Nat
  • 9,820
  • 3
  • 31
  • 33
0

I've had the same problem this week as well, and could not use anonymous types because I needed to implement an interface asking for a List<MyType>. After making a diagram showing all relationships with navigability, I found out that MyType had a bidirectional relationship with MyObject which caused this circular reference, since they both saved each other.

After deciding that MyObject did not really need to know MyType, and thereby making it a unidirectional relationship this problem was solved.

Br2
  • 167
  • 1
  • 10
0

What I have done is a bit radical, but I don't need the property, which makes the nasty circular-reference-causing error, so I have set it to null before serializing.

SessionTickets result = GetTicketsSession();
foreach(var r in result.Tickets)
{
    r.TicketTypes = null; //those two were creating the problem
    r.SelectedTicketType = null;
}
return Json(result);

If you really need your properties, you can create a viewmodel which does not hold circular references, but maybe keeps some Id of the important element, that you could use later for restoring the original value.

ahsteele
  • 26,243
  • 28
  • 134
  • 248
DiSaSteR
  • 608
  • 6
  • 11