Skip to content

JsonSerializer support for circular references #30496

Closed

Description

@josundt

Object/collection graphs containing circular references (or multiple references to the same entity), can be handled in a few different ways when serializing/deserializing to/from JSON.

Best practice is naturally to avoid graphs with circular references when using JSON serialization, but techniques to deal with it exist, and some times it may be needed. We use it a few places in our software.

NewtonSoft.Json supports this, and I guess it is a goal for System.Text.Json to make the feature gap with NewtonSoft as small possible.

During serialization, when an object/collection that has already been serialized is referenced for the second time or more, the common technique I've seen is to replace the repeated objects/collection in the JSON with a

{ "$ref": "some-sort-of-pointer" }

Some use JSONPath to reference the object/array serialized previously in the JSON document.

NewtonSoft.Json uses a different techique with specific serialization settings: It adds an extra "$id" property to all serialized objects in the JSON document, and the "$ref" value is the "$id" property of the object/array serialized previously.

Example using NewtonSoft.Json:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace CircularSerialization
{
    public class Bike
    {
        public List<Tire> Tires { get; set; } = new List<Tire>();
    }

    public class Tire
    {
        public Bike Bike { get; set; }
    }

    static class Program
    {
        static void Main()
        {
            var bike = new Bike();
            bike.Tires.Add(new Tire { Bike = bike });
            bike.Tires.Add(new Tire { Bike = bike });

            var settings = new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };
            var serialized = JsonConvert.SerializeObject(bike, settings);

            Console.Write(serialized);
            // {"$id":"1","Tires":[{"$id":"2","Bike":{"$ref":"1"}},{"$id":"3","Bike":{"$ref":"1"}}]}
        }
    }
}

I guess that if System.Text.Json.JsonSerializer will support circular/multiple references, some more memory allocation will be required during serialization/deserialization when the setting is switched on (so such a setting should naturally be off by default).

Is there a plan to support options for handling circular references in JsonSerializerSettings?
And if so, will you support the "$id" technique or the JSONPath techique, or maybe support both?

Reference:
https://github.com/douglascrockford/JSON-js/blob/master/cycle.js

Activity

  1. ahsonkhan commented on Aug 5, 2019

    @ahsonkhan
    Contributor

    Thanks for filing the issue, @josundt

    However, this is a duplicate of https://github.com/dotnet/corefx/issues/38579.
    It is on our backlog of features to support for vNext.

    I have added your example there instead. Closing.

  2. jozkee commented on Jan 21, 2020

    @jozkee
    Member

    The preview version for ReferenceHandling feature is here.

    var options = new JsonSerializerOptions
    {
        ReferenceHandling = ReferenceHandling.Preserve
    };
    
    string json = JsonSerializer.Serialize(objectWithLoops, options);

    Here's the spec doc including samples and notes about many scenarios.
    If you want to add feedback, feel free to leave a comment in https://github.com/dotnet/corefx/issues/41002.

  3. transferred this issue fromdotnet/corefxon Feb 1, 2020
  4. added this to the 3.0 milestone on Feb 1, 2020
  5. ghost locked as resolved and limited conversation to collaborators on Dec 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions