A circular reference was detected while serializin

2019-01-01 03:22发布

I am trying to do a simple JSON return but I am having issues I have the following below.

public JsonResult GetEventData()
{
    var data = Event.Find(x => x.ID != 0);
    return Json(data);
}

I get a HTTP 500 with the exception as shown in the title of this question. I also tried

var data = Event.All().ToList()

That gave the same problem.

Is this a bug or my implementation?

14条回答
看淡一切
2楼-- · 2019-01-01 04:03

Provided answers are good, but I think they can be improved by adding an "architectural" perspective.

Investigation

MVC's Controller.Json function is doing the job, but it is very poor at providing a relevant error in this case. By using Newtonsoft.Json.JsonConvert.SerializeObject, the error specifies exactly what is the property that is triggering the circular reference. This is particularly useful when serializing more complex object hierarchies.

Proper architecture

One should never try to serialize data models (e.g. EF models), as ORM's navigation properties is the road to perdition when it comes to serialization. Data flow should be the following:

Database -> data models -> service models -> JSON string 

Service models can be obtained from data models using auto mappers (e.g. Automapper). While this does not guarantee lack of circular references, proper design should do it: service models should contain exactly what the service consumer requires (i.e. the properties).

In those rare cases, when the client requests a hierarchy involving the same object type on different levels, the service can create a linear structure with parent->child relationship (using just identifiers, not references).

Modern applications tend to avoid loading complex data structures at once and service models should be slim. E.g.:

  1. access an event - only header data (identifier, name, date etc.) is loaded -> service model (JSON) containing only header data
  2. managed attendees list - access a popup and lazy load the list -> service model (JSON) containing only the list of attendees
查看更多
旧时光的记忆
3楼-- · 2019-01-01 04:04

Using Newtonsoft.Json: In your Global.asax Application_Start method add this line:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
查看更多
孤独总比滥情好
4楼-- · 2019-01-01 04:05

I'm Using the fix, Because Using Knockout in MVC5 views.

On action

return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));

function

   public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
    {
        TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
        foreach (var item in Entity.GetType().GetProperties())
        {
            if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
                item.SetValue(Entity_, Entity.GetPropValue(item.Name));
        }
        return Entity_;  
    }
查看更多
看淡一切
5楼-- · 2019-01-01 04:06

This actually happens because the complex objects are what makes the resulting json object fails. And it fails because when the object is mapped it maps the children, which maps their parents, making a circular reference to occur. Json would take infinite time to serialize it, so it prevents the problem with the exception.

Entity Framework mapping also produces the same behavior, and the solution is to discard all unwanted properties.

Just expliciting the final answer, the whole code would be:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           new {
                Result = (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
               }
           , JsonRequestBehavior.AllowGet
           );
}

It could also be the following in case you don't want the objects inside a Result property:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
           , JsonRequestBehavior.AllowGet
           );
}
查看更多
伤终究还是伤i
6楼-- · 2019-01-01 04:12

An easier alternative to solve this problem is to return an string, and format that string to json with JavaScriptSerializer.

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

It is important the "Select" part, which choose the properties you want in your view. Some object have a reference for the parent. If you do not choose the attributes, the circular reference may appear, if you just take the tables as a whole.

Do not do this:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.toList();
   return j.Serialize(entityList );
}

Do this instead if you don't want the whole table:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

This helps render a view with less data, just with the attributes you need, and makes your web run faster.

查看更多
永恒的永恒
7楼-- · 2019-01-01 04:13

I had the same problem and solved by using Newtonsoft.Json;

var list = JsonConvert.SerializeObject(model,
    Formatting.None,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});

return Content(list, "application/json");
查看更多
登录 后发表回答