Share via


Bevara referenser och hantera eller ignorera cirkelreferenser i System.Text.Json

Den här artikeln visar hur du bevarar referenser och hanterar eller ignorerar cirkelreferenser när du använder System.Text.Json för att serialisera och deserialisera JSON i .NET

Bevara referenser och hantera cirkelreferenser

Om du vill bevara referenser och hantera cirkelreferenser anger du ReferenceHandler till Preserve. Den här inställningen orsakar följande beteende:

  • Vid serialisering:

    När du skriver komplexa typer skriver serialiseraren även metadataegenskaper ($id, $valuesoch $ref).

  • Vid deserialisera:

    Metadata förväntas (även om de inte är obligatoriska) och deserialiseraren försöker förstå det.

Följande kod illustrerar användningen av inställningen Preserve .

using System.Text.Json;
using System.Text.Json.Serialization;

namespace PreserveReferences
{
    public class Employee
    {
        public string? Name { get; set; }
        public Employee? Manager { get; set; }
        public List<Employee>? DirectReports { get; set; }
    }

    public class Program
    {
        public static void Main()
        {
            Employee tyler = new()
            {
                Name = "Tyler Stein"
            };

            Employee adrian = new()
            {
                Name = "Adrian King"
            };

            tyler.DirectReports = [adrian];
            adrian.Manager = tyler;

            JsonSerializerOptions options = new()
            {
                ReferenceHandler = ReferenceHandler.Preserve,
                WriteIndented = true
            };

            string tylerJson = JsonSerializer.Serialize(tyler, options);
            Console.WriteLine($"Tyler serialized:\n{tylerJson}");

            Employee? tylerDeserialized =
                JsonSerializer.Deserialize<Employee>(tylerJson, options);

            Console.WriteLine(
                "Tyler is manager of Tyler's first direct report: ");
            Console.WriteLine(
                tylerDeserialized?.DirectReports?[0].Manager == tylerDeserialized);
        }
    }
}

// Produces output like the following example:
//
//Tyler serialized:
//{
//  "$id": "1",
//  "Name": "Tyler Stein",
//  "Manager": null,
//  "DirectReports": {
//    "$id": "2",
//    "$values": [
//      {
//        "$id": "3",
//        "Name": "Adrian King",
//        "Manager": {
//          "$ref": "1"
//        },
//        "DirectReports": null
//      }
//    ]
//  }
//}
//Tyler is manager of Tyler's first direct report:
//True
Imports System.Text.Json
Imports System.Text.Json.Serialization

Namespace PreserveReferences

    Public Class Employee
        Public Property Name As String
        Public Property Manager As Employee
        Public Property DirectReports As List(Of Employee)
    End Class

    Public NotInheritable Class Program

        Public Shared Sub Main()
            Dim tyler As New Employee

            Dim adrian As New Employee

            tyler.DirectReports = New List(Of Employee) From {
                adrian}
            adrian.Manager = tyler

            Dim options As New JsonSerializerOptions With {
                .ReferenceHandler = ReferenceHandler.Preserve,
                .WriteIndented = True
            }

            Dim tylerJson As String = JsonSerializer.Serialize(tyler, options)
            Console.WriteLine($"Tyler serialized:{tylerJson}")

            Dim tylerDeserialized As Employee = JsonSerializer.Deserialize(Of Employee)(tylerJson, options)

            Console.WriteLine(
                "Tyler is manager of Tyler's first direct report: ")
            Console.WriteLine(
                tylerDeserialized.DirectReports(0).Manager Is tylerDeserialized)
        End Sub

    End Class

End Namespace

' Produces output like the following example:
'
'Tyler serialized:
'{
'  "$id": "1",
'  "Name": "Tyler Stein",
'  "Manager": null,
'  "DirectReports": {
'    "$id": "2",
'    "$values": [
'      {
'        "$id": "3",
'        "Name": "Adrian King",
'        "Manager": {
'          "$ref": "1"
'        },
'        "DirectReports": null
'      }
'    ]
'  }
'}
'Tyler is manager of Tyler's first direct report:
'True

Den här funktionen kan inte användas för att bevara värdetyper eller oföränderliga typer. Vid deserialisering skapas instansen av en oföränderlig typ när hela nyttolasten har lästs. Det skulle därför vara omöjligt att deserialisera samma instans om en referens till den visas inom JSON-nyttolasten.

För värdetyper, oföränderliga typer och matriser serialiseras inga referensmetadata. Vid deserialisering utlöses ett undantag om $ref eller $id hittas. Värdetyper ignorerar $id dock (och $values när det gäller samlingar) för att göra det möjligt att deserialisera nyttolaster som serialiserades med hjälp Newtonsoft.Jsonav . Newtonsoft.Json serialiserar metadata för sådana typer.

För att avgöra om objekt är lika System.Text.Json använder ReferenceEqualityComparer.Instance, som använder referensjämlikhet (Object.ReferenceEquals(Object, Object)) i stället för värdejämlikhet (Object.Equals(Object)) vid jämförelse av två objektinstanser.

Mer information om hur referenser serialiseras och deserialiseras finns i ReferenceHandler.Preserve.

Klassen ReferenceResolver definierar beteendet att bevara referenser för serialisering och deserialisering. Skapa en härledd klass för att ange anpassat beteende. Ett exempel finns i GuidReferenceResolver.

Spara referensmetadata över flera serialiserings- och deserialiseringsanrop

Som standard cachelagras endast referensdata för varje anrop till Serialize eller Deserialize. Om du vill spara referenser från ett Serialize/Deserialize anrop till ett annat rotar du instansen ReferenceResolver på anropsplatsen Serialize/Deserializeför . Följande kod visar ett exempel för det här scenariot:

  • Du har en lista över Employee objekt och du måste serialisera var och en individuellt.
  • Du vill dra nytta av de referenser som sparats i matcharen för ReferenceHandler.

Här är Employee klassen:

public class Employee
{
    public string? Name { get; set; }
    public Employee? Manager { get; set; }
    public List<Employee>? DirectReports { get; set; }
}

En klass som härleds från ReferenceResolver lagrar referenserna i en ordlista:

class MyReferenceResolver : ReferenceResolver
{
    private uint _referenceCount;
    private readonly Dictionary<string, object> _referenceIdToObjectMap = [];
    private readonly Dictionary<object, string> _objectToReferenceIdMap = new (ReferenceEqualityComparer.Instance);

    public override void AddReference(string referenceId, object value)
    {
        if (!_referenceIdToObjectMap.TryAdd(referenceId, value))
        {
            throw new JsonException();
        }
    }

    public override string GetReference(object value, out bool alreadyExists)
    {
        if (_objectToReferenceIdMap.TryGetValue(value, out string? referenceId))
        {
            alreadyExists = true;
        }
        else
        {
            _referenceCount++;
            referenceId = _referenceCount.ToString();
            _objectToReferenceIdMap.Add(value, referenceId);
            alreadyExists = false;
        }

        return referenceId;
    }

    public override object ResolveReference(string referenceId)
    {
        if (!_referenceIdToObjectMap.TryGetValue(referenceId, out object? value))
        {
            throw new JsonException();
        }

        return value;
    }
}

En klass som härleds från ReferenceHandler innehåller en instans av MyReferenceResolver och skapar endast en ny instans när det behövs (i en metod med namnet Reset i det här exemplet):

class MyReferenceHandler : ReferenceHandler
{
    public MyReferenceHandler() => Reset();
    private ReferenceResolver? _rootedResolver;
    public override ReferenceResolver CreateResolver() => _rootedResolver!;
    public void Reset() => _rootedResolver = new MyReferenceResolver();
}

När exempelkoden anropar serialiseraren använder den ReferenceHandler en JsonSerializerOptions instans där egenskapen är inställd på en instans av MyReferenceHandler. När du följer det här mönstret måste du återställa ReferenceResolver ordlistan när du är klar med serialiseringen, så att den inte växer för alltid.

var options = new JsonSerializerOptions
{
    WriteIndented = true
};
var myReferenceHandler = new MyReferenceHandler();
options.ReferenceHandler = myReferenceHandler;

string json;
foreach (Employee emp in employees)
{
    json = JsonSerializer.Serialize(emp, options);
    DoSomething(json);
}

// Reset after serializing to avoid out of bounds memory growth in the resolver.
myReferenceHandler.Reset();

Ignorera cirkelreferenser

I stället för att hantera cirkelreferenser kan du ignorera dem. Om du vill ignorera cirkelreferenser anger du ReferenceHandler till IgnoreCycles. Serialiseraren anger cirkelreferensegenskaper till null, som du ser i följande exempel:

using System.Text.Json;
using System.Text.Json.Serialization;

namespace SerializeIgnoreCycles
{
    public class Employee
    {
        public string? Name { get; set; }
        public Employee? Manager { get; set; }
        public List<Employee>? DirectReports { get; set; }
    }

    public class Program
    {
        public static void Main()
        {
            Employee tyler = new()
            {
                Name = "Tyler Stein"
            };

            Employee adrian = new()
            {
                Name = "Adrian King"
            };

            tyler.DirectReports = new List<Employee> { adrian };
            adrian.Manager = tyler;

            JsonSerializerOptions options = new()
            {
                ReferenceHandler = ReferenceHandler.IgnoreCycles,
                WriteIndented = true
            };

            string tylerJson = JsonSerializer.Serialize(tyler, options);
            Console.WriteLine($"Tyler serialized:\n{tylerJson}");

            Employee? tylerDeserialized =
                JsonSerializer.Deserialize<Employee>(tylerJson, options);

            Console.WriteLine(
                "Tyler is manager of Tyler's first direct report: ");
            Console.WriteLine(
                tylerDeserialized?.DirectReports?[0]?.Manager == tylerDeserialized);
        }
    }
}

// Produces output like the following example:
//
//Tyler serialized:
//{
//  "Name": "Tyler Stein",
//  "Manager": null,
//  "DirectReports": [
//    {
//      "Name": "Adrian King",
//      "Manager": null,
//      "DirectReports": null
//    }
//  ]
//}
//Tyler is manager of Tyler's first direct report:
//False

I föregående exempel Manager serialiseras under Adrian King för null att undvika cirkelreferensen. Det här beteendet har följande fördelar jämfört ReferenceHandler.Preservemed :

  • Det minskar nyttolaststorleken.
  • Den skapar JSON som är begriplig för andra serialiserare än System.Text.Json och Newtonsoft.Json.

Det här beteendet har följande nackdelar:

  • Tyst dataförlust.
  • Data kan inte göra en tur- och returresa från JSON tillbaka till källobjektet.

Se även