Handling reference loops in JSON.net

☆樱花仙子☆ 提交于 2021-02-07 14:38:19

问题


I wish to serialize a collection (List<Item>) of items to JSON.

These items have a collection of Connection which gives information about the connection from one Item to a second Item. And since the connection object has a reference to the items it makes it an infinite loop.

My question is is there a way for me to skip serialization of the connection collection when serializing the object the second time.

I've tried things like inheriting from JsonConverter and writing a custom WriteJson() method but from there I have no sence whether I should write out the array or not.

I've also tried using a custom ContractResolver but with no good results.


Classes

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


Wanted result:

[
    {
        "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
            }
        }
    }
]



EDIT:

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

回答1:


Instead of declaring the type of Source and Target as of Item, you may introduce a new class, say, ItemClass, which'll contain all the fields of class Item, except the Connections property.

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

    // ...
}

Now you'll have an overhead of populating the new ItemClass type instance accordingly.




回答2:


Add this to your Global.asax (or in the WebApiConfig or any other config class)

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



回答3:


If I'm not mistaken you need to keep only the first depth of reference containing the connection collection? If that is the case, try using:

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



回答4:


There is an issue, since objects can't be referenced in Json (see Standard way of referencing an object by identity (for, eg, circular references)?).

This means you have to duplicate an item for each time it occurs in a reference to bring all the connections to the client.

I suggest to bring the connections to the client using only ids, with the connections as a seperate object.

Add the JsonIgnore attribute to the Connections property on the Item.

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

And use a class to send to the client instead of the list of items directly.

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


来源:https://stackoverflow.com/questions/22297173/handling-reference-loops-in-json-net

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!