JSON.NET 检测到类型的错误自引用循环

本文关键字:错误 自引用 循环 类型 NET 检测 JSON | 更新日期: 2023-09-27 18:08:29

>我试图序列化从实体数据模型.edmx自动生成的POCO类,当我使用

JsonConvert.SerializeObject 

我收到以下错误:

检测到 System.data.entity 类型的自引用循环出错。

如何解决此问题?

JSON.NET 检测到类型的错误自引用循环

使用 JsonSerializerSettings

  • 如果遇到引用循环,ReferenceLoopHandling.Error(默认值(将出错。这就是您获得例外的原因。
  • 如果对象是嵌套的,ReferenceLoopHandling.Serialize很有用,但不是无限期的。
  • 如果对象是自身的子对象,则ReferenceLoopHandling.Ignore不会序列化该对象。

例:

JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented, 
new JsonSerializerSettings 
{ 
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize
});

如果必须无限期序列化嵌套的对象,则可以使用 PreserveObjectReferences 来避免 StackOverflowException。

例:

JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented, 
new JsonSerializerSettings 
{ 
        PreserveReferencesHandling = PreserveReferencesHandling.Objects
});

选择对要序列化的对象有意义的内容。

参考 http://james.newtonking.com/json/help/

解决方法是忽略循环引用,而不是序列化它们。此行为在 JsonSerializerSettings 中指定。

带过载的JsonConvert

JsonConvert.SerializeObject(YourObject, Formatting.Indented,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
    }
);

全局设置,在 Global.asax 中Application_Start()代码.cs:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
     Formatting = Newtonsoft.Json.Formatting.Indented,
     ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
};

参考: https://github.com/JamesNK/Newtonsoft.Json/issues/78

最简单的

方法是安装 Json.NET 从 nuget 并将 [JsonIgnore] 属性添加到类中的虚拟属性,例如:

    public string Name { get; set; }
    public string Description { get; set; }
    public Nullable<int> Project_ID { get; set; }
    [JsonIgnore]
    public virtual Project Project { get; set; }

尽管这些天,我创建了一个模型,其中只包含我想要传递的属性,因此它更轻,不包括不需要的集合,并且在重建生成的文件时不会丢失更改......

在 .NET Core 1.0 中,可以在 Startup.cs 文件中将其设置为全局设置:

using System.Buffers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Newtonsoft.Json;
// beginning of Startup class
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.OutputFormatters.Clear();
            options.OutputFormatters.Add(new JsonOutputFormatter(new JsonSerializerSettings(){
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            }, ArrayPool<char>.Shared));
        });
    }

如果您使用的是 .NET Core 2.x,请更新Startup.cs中的ConfigureServices部分

https://learn.microsoft.com/en-us/ef/core/querying/related-data/serialization

    public void ConfigureServices(IServiceCollection services)
    {
    ...
    services.AddMvc()
        .AddJsonOptions(options => 
          options.SerializerSettings.ReferenceLoopHandling = 
            Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );
    ...
    }

如果您使用的是 .NET Core 3.x - 5.0,没有 MVC,它将是:

# startup.cs
services.AddControllers()
  .AddNewtonsoftJson(options =>
      options.SerializerSettings.ReferenceLoopHandling =
        Newtonsoft.Json.ReferenceLoopHandling.Ignore
   );

对于 .NET 6.0,唯一的区别是它现在进入 Program.cs .

# program.cs
services.AddControllers()
   .AddNewtonsoftJson(options =>
      options.SerializerSettings.ReferenceLoopHandling = 
        Newtonsoft.Json.ReferenceLoopHandling.Ignore);

如果使用实体框架和数据库优先设计模式,则此引用循环处理几乎是必需的。

我们可以将这两行添加到 DbContext 类构造函数中以禁用自引用循环,例如

public TestContext()
        : base("name=TestContext")
{
    this.Configuration.LazyLoadingEnabled = false;
    this.Configuration.ProxyCreationEnabled = false;
}

当你遇到循环问题时,要序列化我们的牛顿软件json,就我而言,我不需要修改global.asax或apiconfig。我只是使用 JsonSerializesSettings 忽略循环处理。

JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var lst = db.shCards.Where(m => m.CardID == id).ToList();
string json = JsonConvert.SerializeObject(lst, jss);

您也可以将属性应用于属性。[JsonProperty( ReferenceLoopHandling = ... )]属性非常适合这一点。

例如:

/// <summary>
/// Represents the exception information of an event
/// </summary>
public class ExceptionInfo
{
    // ...code omitted for brevity...
    /// <summary>
    /// An inner (nested) error.
    /// </summary>
    [JsonProperty( ReferenceLoopHandling = ReferenceLoopHandling.Ignore, IsReference = true )]
    public ExceptionInfo Inner { get; set; }
    // ...code omitted for brevity...    
}

希望有帮助,Jaans

要在 MVC 6 中忽略循环引用并且不全局序列化它们,请在 startup.cs 中使用以下命令:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().Configure<MvcOptions>(options =>
        {
            options.OutputFormatters.RemoveTypesOf<JsonOutputFormatter>();
            var jsonOutputFormatter = new JsonOutputFormatter();
            jsonOutputFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            options.OutputFormatters.Insert(0, jsonOutputFormatter);
        });
    }

只需更新服务。AddControllers(( in Startup.cs file

services.AddControllers()
  .AddNewtonsoftJson(options =>
      options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
   );

WebApiConfig.cs类中使用它:

var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);

对我来说,我必须走一条不同的路线。 我没有尝试修复 JSON.Net 序列化程序,而是不得不在我的数据上下文上进行延迟加载。

刚刚将其添加到我的基本存储库中:

context.Configuration.ProxyCreationEnabled = false;

"context"对象是我在基本存储库中使用的构造函数参数,因为我使用依赖注入。 您可以在实例化数据上下文的任何位置更改 ProxyCreationEnabled 属性。

http://techie-tid-bits.blogspot.com/2015/09/jsonnet-serializer-and-error-self.html

我有这个例外,我的工作解决方案是简单易行的,

通过向其添加 JsonIgnore 属性来忽略引用属性:

[JsonIgnore]
public MyClass currentClass { get; set; }

反序列化时重置属性:

Source = JsonConvert.DeserializeObject<MyObject>(JsonTxt);
foreach (var item in Source)
        {
            Source.MyClass = item;
        }

使用Newtonsoft.Json;

团队:

这适用于 ASP.NET 核心;上述挑战在于如何"设置忽略"。 根据您设置应用程序的方式,这可能非常具有挑战性。这是对我有用的。

这可以放在您的公共无效配置服务(IServiceCollection服务(部分。

services.AddMvc().AddJsonOptions(opt => 
        { 
      opt.SerializerSettings.ReferenceLoopHandling =
      Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        });

人们已经讨论过将 [JsonIgnore] 添加到类中的虚拟属性中,例如:

[JsonIgnore]
public virtual Project Project { get; set; }

我还将分享另一个选项,[JsonProperty(NullValueHandling = NullValueHandling.Ignore(],仅当属性为空时,它才会从序列化中省略该属性:

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public virtual Project Project { get; set; }

在 .Net 5.x 中,使用以下代码在启动时更新 ConfigureServices 方法.cs

public void ConfigureServices(IServiceCollection services)
{
    ----------------
    ----------------
    services.AddMvc().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
    });
    ------------------
}

默认情况下,序列化 (System.Text.Json.Serialization( 不支持具有循环的对象,并且不保留重复引用。使用"保留">在序列化时启用唯一对象引用保留,并使用元数据在反序列化时读取保留的引用。MSDN Link

C# 代码:

var jsonSerializerSettings = new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
};
var jsonString = JsonConvert.SerializeObject(object2Serialize, jsonSerializerSettings);
var filePath = @"E:'json.json";
File.WriteAllText(filePath, jsonString);
<</div> div class="answers">我

的问题解决了自定义配置 JsonSerializerSettings:

services.AddMvc(
  // ...
               ).AddJsonOptions(opt =>
                 {
                opt.SerializerSettings.ReferenceLoopHandling =
                    Newtonsoft.Json.ReferenceLoopHandling.Serialize;
                opt.SerializerSettings.PreserveReferencesHandling =
                    Newtonsoft.Json.PreserveReferencesHandling.Objects;
                 });

对于 .NET Core 3.0,请更新 Startup.cs 类,如下所示。

public void ConfigureServices(IServiceCollection services)
{
...
services.AddControllers()
    .AddNewtonsoftJson(
        options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
    );
...
}

请参阅:https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-core-3-0-preview-5/

还请确保在方法中使用 await 和 async。如果对象未正确序列化,则可能会出现此错误。

我继承了一个数据库应用程序,它将数据模型提供给网页。 默认情况下,序列化将尝试遍历整个模型树,这里的大多数答案都是如何防止这种情况的良好开端。

尚未探索的一种选择是使用接口来提供帮助。 我将从前面的例子中窃取:

public partial class CompanyUser
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public int UserId { get; set; }
    public virtual Company Company { get; set; }
    public virtual User User { get; set; }
}
public interface IgnoreUser
{
    [JsonIgnore]
    User User { get; set; }
}
public interface IgnoreCompany
{
    [JsonIgnore]
    User User { get; set; }
}
public partial class CompanyUser : IgnoreUser, IgnoreCompany
{
}

在上述解决方案中,没有 Json 设置受到损害。 将 LazyLoadEnabled 和/或 ProxyCreationEnabled 设置为 false 会影响您的所有后端编码,并阻止 ORM 工具的一些真正好处。 根据您的应用程序,延迟加载/代理创建设置可以阻止加载导航属性,而无需手动加载它们。

这是一个更好的解决方案,用于防止导航属性序列化,它使用标准 json 功能:如何使 JSON 序列化程序忽略导航属性?

只需将Configuration.ProxyCreationEnabled = false;放在上下文文件中;这将解决问题。

public demEntities()
    : base("name=demEntities")
{
    Configuration.ProxyCreationEnabled = false;
}

我遇到了同样的问题,我尝试使用 JsonSetting 来忽略自引用错误,直到我得到一个非常深入的自引用类,我的 dot-net 过程挂在 Json 写入值上。

我的问题

    public partial class Company : BaseModel
{
    public Company()
    {
        CompanyUsers = new HashSet<CompanyUser>();
    }
    public string Name { get; set; }
    public virtual ICollection<CompanyUser> CompanyUsers { get; set; }
}
public partial class CompanyUser
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public int UserId { get; set; }
    public virtual Company Company { get; set; }
    public virtual User User { get; set; }
}
public partial class User : BaseModel
{
    public User()
    {
        CompanyUsers = new HashSet<CompanyUser>();
    }
    public string DisplayName { get; set; }
    public virtual ICollection<CompanyUser> CompanyUsers { get; set; }
}

您可以在用户类中看到问题,它引用了 CompanyUser 类,这是一个自我引用。

现在,我调用包含所有关系属性的 GetAll 方法。

cs.GetAll("CompanyUsers", "CompanyUsers.User");

在这个阶段,我的DotNetCore进程挂在执行JsonResult上,写入值......永远不会到来。在我的Startup.cs中,我已经设置了JsonOption。出于某种原因,EFCore 包含嵌套属性,我不要求 Ef 提供。

    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

预期行为应为此

嘿,EfCore,您能否在我的数据中包含"公司用户"数据 公司类,以便我可以轻松访问数据。

然后

嘿,EfCore,您能否将"公司用户.用户">数据作为 好吧,这样我就可以轻松访问这样的数据 Company.CompanyUsers.First((.用户.显示名称

在这个阶段,我应该只得到这个"公司.公司用户.第一((。User.DisplayName">,它不应该给我 Company.CompanyUsers.First((。用户.公司导致自引用问题的用户;从技术上讲,它不应该给我User.CompanyUsers,因为CompanyUsers是一个导航属性。但是,EfCore非常兴奋,给了我User.CompanyUsers

因此,我决定为要从对象中排除的属性编写一个扩展方法(它实际上并不排除它只是将属性设置为 null(。不仅如此,它还适用于数组属性。下面是我还将为其他用户导出 nuget 包的代码(不确定这是否对某人有帮助(。原因很简单,因为我懒得写.Select(n => new { n.p1, n.p2}(;我只是不想写选择语句来排除 1 个属性!

这不是最好的代码(我会在某个阶段更新(,因为我匆忙编写,尽管这可能会帮助那些想要在带有数组的对象中排除(设置 null(的人。

    public static class PropertyExtensions
{
    public static void Exclude<T>(this T obj, Expression<Func<T, object>> expression)
    {
        var visitor = new PropertyVisitor<T>();
        visitor.Visit(expression.Body);
        visitor.Path.Reverse();
        List<MemberInfo> paths = visitor.Path;
        Action<List<MemberInfo>, object> act = null;
        int recursiveLevel = 0;
        act = (List<MemberInfo> vPath, object vObj) =>
        {
            // set last propert to null thats what we want to avoid the self-referencing error.
            if (recursiveLevel == vPath.Count - 1)
            {
                if (vObj == null) throw new ArgumentNullException("Object cannot be null");
                vObj.GetType().GetMethod($"set_{vPath.ElementAt(recursiveLevel).Name}").Invoke(vObj, new object[] { null });
                return;
            }
            var pi = vObj.GetType().GetProperty(vPath.ElementAt(recursiveLevel).Name);
            if (pi == null) return;
            var pv = pi.GetValue(vObj, null);
            if (pi.PropertyType.IsArray || pi.PropertyType.Name.Contains("HashSet`1") || pi.PropertyType.Name.Contains("ICollection`1"))
            {
                var ele = (IEnumerator)pv.GetType().GetMethod("GetEnumerator").Invoke(pv, null);
                while (ele.MoveNext())
                {
                    recursiveLevel++;
                    var arrItem = ele.Current;
                    act(vPath, arrItem);
                    recursiveLevel--;
                }
                if (recursiveLevel != 0) recursiveLevel--;
                return;
            }
            else
            {
                recursiveLevel++;
                act(vPath, pv);
            }
            if (recursiveLevel != 0) recursiveLevel--;
        };
        // check if the root level propert is array
        if (obj.GetType().IsArray)
        {
            var ele = (IEnumerator)obj.GetType().GetMethod("GetEnumerator").Invoke(obj, null);
            while (ele.MoveNext())
            {
                recursiveLevel = 0;
                var arrItem = ele.Current;
                act(paths, arrItem);
            }
        }
        else
        {
            recursiveLevel = 0;
            act(paths, obj);
        }
    }
    public static T Explode<T>(this T[] obj)
    {
        return obj.FirstOrDefault();
    }
    public static T Explode<T>(this ICollection<T> obj)
    {
        return obj.FirstOrDefault();
    }
}

上面的扩展类将使您能够将属性设置为 null,以避免自引用循环偶数组。

表达式生成器

    internal class PropertyVisitor<T> : ExpressionVisitor
{
    public readonly List<MemberInfo> Path = new List<MemberInfo>();
    public Expression Modify(Expression expression)
    {
        return Visit(expression);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (!(node.Member is PropertyInfo))
        {
            throw new ArgumentException("The path can only contain properties", nameof(node));
        }
        Path.Add(node.Member);
        return  base.VisitMember(node);
    }
}

用法:

模型类

    public class Person
{
    public string Name { get; set; }
    public Address AddressDetail { get; set; }
}
public class Address
{
    public string Street { get; set; }
    public Country CountryDetail { get; set; }
    public Country[] CountryDetail2 { get; set; }
}
public class Country
{
    public string CountryName { get; set; }
    public Person[] CountryDetail { get; set; }
}

虚拟数据

           var p = new Person
        {
            Name = "Adeel Rizvi",
            AddressDetail = new Address
            {
                Street = "Sydney",
                CountryDetail = new Country
                {
                    CountryName = "AU"
                }
            }
        };
        var p1 = new Person
        {
            Name = "Adeel Rizvi",
            AddressDetail = new Address
            {
                Street = "Sydney",
                CountryDetail2 = new Country[]
                {
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                }
            }
        };

例:

情况 1:仅排除没有任何数组的属性

p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);

情况 2:使用 1 个数组排除属性

p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);

情况 3:排除具有 2 个嵌套数组的属性

p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);

事例 4:包含的 EF GetAll 查询

var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;

您已经注意到 Explode(( 方法也是一个扩展方法,仅用于我们的表达式生成器从数组属性中获取属性。只要有数组属性,请使用 .爆炸((。您的属性要排除或 .爆炸((。Property1.MyArrayProperty.Explode((.我的愚蠢财产。上面的代码帮助我避免自我引用,就像我想要的深度一样深。现在我可以使用 GetAll 并排除我不想要的属性!

感谢您阅读这篇大文章!

与这里的其他人非常相似,通过使用序列化程序设置解决。 然而,我的起源是因为我正在使用 FromObject 方面的东西,因为我需要一个 JObject 来使用。

这意味着只需要创建默认序列化程序并对其应用设置。简单的修复变成这个

 var deserializerSettings = new JsonSerializerSettings()
        {
            ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
            PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        };
        var serializer = JsonSerializer.CreateDefault(deserializerSettings);
            
        JObject jsonObject = (JObject)JToken.FromObject(mySelfRefernceObject, serializer);

此问题来自第三方 nuget 包之一。没有更改配置或序列化的选项。在我的消费者项目中安装 Newtonsoft.Json 包解决了这个问题。

<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<</div> div class="answers">

因为没有循环这对我有用-
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,

我已经在这里解决了所有问题 - 使用 .Net Core 2 WebAPI 进行实体框架子序列化https://gist.github.com/Kaidanov/f9ad0d79238494432f32b8407942c606

将不胜感激任何评论。也许有人可以在某个时候使用它。

我喜欢从

Application_Start()开始的解决方案,就像这里的答案一样

显然,我无法使用函数中的配置访问 JavaScript 中的 json 对象,就像 DalSoft 的答案一样,因为返回的对象在对象的(键,val(上都有"' ''r"。

无论如何,任何有效的方法都是很好的(因为根据提出的评论和问题,不同的方法在不同的场景中工作(,尽管最好使用一些支持该方法的良好文档来执行标准方法。