【问题标题】:Handling reference loops in JSON.net处理 JSON.net 中的引用循环
【发布时间】:2014-04-13 09:06:30
【问题描述】:

我希望将项目的集合 (List<Item>) 序列化为 JSON。

这些项目具有Connection 的集合,它提供有关从一个Item 到第二个Item 的连接的信息。而且由于连接对象具有对项目的引用,因此它使其成为无限循环。

我的问题是有没有办法让我在第二次序列化对象时跳过连接集合的序列化。

我尝试过从 JsonConverter 继承并编写自定义 WriteJson() 方法,但从那里我不知道是否应该写出数组。

我也尝试过使用自定义 ContractResolver,但效果不佳。


类

public class Item
{
    private static int _lastID = 0;

    public Item()
    {
        ID = ++_lastID;
        Connections = new List<Connection>();
    }


    public int ID { get; set; }

    public string Name { get; set; }

    public string Prop1 { get; set; }

    public string Prop2 { get; set; }

    public List<Connection> Connections { get; set; }

}



public class Connection
{
    private Connection(ConnectionType type, Item source, Item target)
    {
        if (type == ConnectionType.None)
            throw new ArgumentException();
        if (source == null)
            throw new ArgumentNullException("source");
        if (target == null)
            throw new ArgumentNullException("target");

        Type = type;
        Source = source;
        Target = target;
    }


    public ConnectionType Type { get; set; }

    public Item Source { get; set; }

    public Item Target { get; set; }


    public static void Connect(ConnectionType type, Item source, Item target)
    {
        var conn = new Connection(type, source, target);
        source.Connections.Add(conn);
        target.Connections.Add(conn);
    }
}


想要的结果:

[
    {
        "id": 1,
        "name": "Item #1",
        "prop1": "val1",
        "prop2": "val2",
        "connections": {
            "type": "ConnType",
            "source": {
                "id": 1,
                "name": "Item #1",
                "prop1": "val1",
                "prop2": "val2"
                // no connections array
            },
            "target": {
                "id": 2,
                "name": "Item #2",
                "prop1": "val1",
                "prop2": "val2"
                // no connections array
            }
        }
    },
    {
        "id": 2,
        "name": "Item #2",
        "prop1": "val1",
        "prop2": "val2",
        "connections": {
            "type": "ConnType",
            "source": {
                "id": 1,
                "name": "Item #1",
                "prop1": "val1",
                "prop2": "val2"
                // no connections array
            },
            "target": {
                "id": 2,
                "name": "Item #2",
                "prop1": "val1",
                "prop2": "val2"
                // no connections array
            }
        }
    }
]



编辑:

C#

var settings = new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
        Formatting = Formatting.Indented
    };
settings.Converters.Add(new StringEnumConverter());
var json = JsonConvert.SerializeObject(collection, settings);

【问题讨论】:

    标签: c# json json.net


    【解决方案1】:

    如果我没记错的话,您只需要保留包含连接集合的第一个参考深度吗?如果是这种情况,请尝试使用:

    settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
    settings.MaxDepth = 1;
    

    【讨论】:

      【解决方案2】:

      有一个问题,因为无法在 Json 中引用对象(请参阅Standard way of referencing an object by identity (for, eg, circular references)?)。

      这意味着您必须为每次在引用中出现的项目复制一个项目,以将所有连接带到客户端。

      我建议仅使用 id 将连接带到客户端,并将连接作为单独的对象。

      将JsonIgnore 属性添加到Item 上的Connections 属性。

      [JsonIgnore]
      public List<Connection> Connections { get; set; }
      

      并使用类而不是直接发送项目列表。

          class ConnectionContainer
          {
              private readonly List<Item> _items;
              private readonly List<ConnectionInfo> _connections;
      
              public ConnectionContainer(IEnumerable<Item> items)
              {
                  _items = items.ToList();
                  Connections = items.SelectMany(i => i.Connections).Distinct().Select(c => new ConnectionInfo
                  {
                      Type = c.Type,
                      SourceId = c.Source.ID,
                      TargetId = c.Target.ID
                  }).ToList();
              }
      
              public List<Item> Items
              {
                  get { return _items; }
              }
      
              public List<ConnectionInfo> Connections
              {
                  get { return _connections; }
              }
          }
      
          class ConnectionInfo
          {
              private ConnectionType Type { get; set; }
              private int SourceId { get; set; }
              private int TargetId { get; set; }
          }
      

      【讨论】:

      • 如果我使用这个属性,我会丢失所有的连接信息,我希望它只在第一个对象上有信息
      • 由于这些类在一个单独的项目中,我想避免引用 Newtonsoft。
      【解决方案3】:

      将此添加到您的 Global.asax(或在 WebApiConfig 或任何其他配置类中)

      var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
      jsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
      

      【讨论】:

      • 如果之前尝试过与ReferenceLoopHandling 混合但没有成功,但现在当我再次尝试时,它给了我想要的结果。感谢您为我指出正确的方向。虽然我使用JsonConvert.SerializeObject(collection, settings);
      • 现在我想起了为什么它不适用于ReferenceLoopHandling.Ignore。连接对象只有一个引用,源或目标取决于它从哪个对象序列化。这对我不起作用,所以我最终会得到@Wasif 的解决方案
      【解决方案4】:

      您可以引入一个新类,例如 ItemClass,而不是将 Source 和 Target 的类型声明为 Item,它将包含类 Item 的所有字段,除了Connections 财产。

      public class ItemClass
      {
          public int ID { get; set; }
      
          public string Name { get; set; }
      
          public string Prop1 { get; set; }
      
          public string Prop2 { get; set; }
      }
      
      public class Connection
      {
          // ...
      
          public ConnectionType Type { get; set; }
      
          public ItemClass Source { get; set; }
      
          public ItemClass Target { get; set; }
      
          // ...
      }
      

      现在您将需要相应地填充新的 ItemClass 类型实例。

      【讨论】:

      • 我喜欢这个解决方案,但由于它引入了更多的复杂性,我会选择@Moeri 解决方案
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-12
      • 2012-11-10
      • 2015-03-02
      • 2016-09-04
      • 1970-01-01
      • 2019-02-26
      • 1970-01-01
      相关资源
      最近更新 更多