Delen via


Verwijzingen behouden en kringverwijzingen verwerken of negeren in System.Text.Json

In dit artikel wordt beschreven hoe u verwijzingen kunt behouden en kringverwijzingen kunt behouden of negeren terwijl System.Text.Json u JSON in .NET serialiseert en deserialiseert

Verwijzingen behouden en kringverwijzingen verwerken

Als u verwijzingen wilt behouden en kringverwijzingen wilt verwerken, stelt u deze in ReferenceHandler op Preserve. Deze instelling veroorzaakt het volgende gedrag:

  • Bij serialiseren:

    Bij het schrijven van complexe typen schrijft de serializer ook metagegevenseigenschappen ($id, $valuesen).$ref

  • Bij het deserialiseren:

    Metagegevens worden verwacht (hoewel niet verplicht) en de deserializer probeert deze te begrijpen.

De volgende code illustreert het gebruik van de Preserve instelling.

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

Deze functie kan niet worden gebruikt om waardetypen of onveranderbare typen te behouden. Bij deserialisatie wordt het exemplaar van een onveranderbaar type gemaakt nadat de hele nettolading is gelezen. Het is dus onmogelijk om hetzelfde exemplaar te deserialiseren als er een verwijzing naar het exemplaar wordt weergegeven in de JSON-nettolading.

Voor waardetypen, onveranderbare typen en matrices worden geen verwijzingsmetagegevens geserialiseerd. Bij deserialisatie wordt een uitzondering gegenereerd als $ref of $id wordt gevonden. Waardetypen negeren $id echter (en $values in het geval van verzamelingen) om het mogelijk te maken nettoladingen te deserialiseren die zijn geserialiseerd met behulp van Newtonsoft.Json. Newtonsoft.Json serialiseert metagegevens voor dergelijke typen.

Als u wilt bepalen of objecten gelijk zijn, System.Text.Json gebruikt ReferenceEqualityComparer.Instanceu , die gebruikmaakt van verwijzings gelijkheid (Object.ReferenceEquals(Object, Object)) in plaats van waarde gelijkheid (Object.Equals(Object)) bij het vergelijken van twee objectexemplaren.

Zie voor meer informatie over hoe verwijzingen worden geserialiseerd en gedeserialiseerd ReferenceHandler.Preserve.

De ReferenceResolver klasse definieert het gedrag van het behouden van verwijzingen over serialisatie en deserialisatie. Maak een afgeleide klasse om aangepast gedrag op te geven. Zie GuidReferenceResolver voor een voorbeeld.

Referentiemetagegevens behouden voor meerdere serialisatie- en deserialisatieaanroepen

Referentiegegevens worden standaard alleen in de cache opgeslagen voor elke aanroep naar Serialize of Deserialize. Als u verwijzingen van de ene Serialize/Deserialize aanroep naar de andere wilt behouden, moet u het ReferenceResolver exemplaar in de aanroepsite van .Serialize/Deserialize De volgende code toont een voorbeeld voor dit scenario:

  • U hebt een lijst Employee met objecten en u moet elk afzonderlijk serialiseren.
  • U wilt profiteren van de verwijzingen die zijn opgeslagen in de resolver voor de ReferenceHandler.

Dit is de Employee klasse:

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

Een klasse die is afgeleid van de opslag van ReferenceResolver de verwijzingen in een woordenlijst:

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

Een klasse die is afgeleid van ReferenceHandler een exemplaar van MyReferenceResolver en maakt alleen een nieuw exemplaar wanneer dat nodig is (in een methode die in dit voorbeeld wordt genoemd Reset ):

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

Wanneer de voorbeeldcode de serializer aanroept, wordt er een JsonSerializerOptions exemplaar gebruikt waarin de ReferenceHandler eigenschap is ingesteld op een exemplaar van MyReferenceHandler. Wanneer u dit patroon volgt, moet u de ReferenceResolver woordenlijst opnieuw instellen wanneer u klaar bent met serialiseren, zodat u het niet voor altijd kunt laten groeien.

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

Kringverwijzingen negeren

In plaats van kringverwijzingen te verwerken, kunt u deze negeren. Als u kringverwijzingen wilt negeren, stelt u deze in ReferenceHandler op IgnoreCycles. De serializer stelt kringverwijzingseigenschappen in op null, zoals wordt weergegeven in het volgende voorbeeld:

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

In het voorgaande voorbeeld Manager wordt onder Adrian King geserialiseerd null om de kringverwijzing te voorkomen. Dit gedrag heeft de volgende voordelen ten opzichte ReferenceHandler.Preservevan:

  • De nettolading wordt kleiner.
  • Er wordt JSON gemaakt die begrijpelijk is voor andere serialisatiemiddelen dan System.Text.Json en Newtonsoft.Json.

Dit gedrag heeft de volgende nadelen:

  • Stil verlies van gegevens.
  • Gegevens kunnen geen retour van JSON naar het bronobject maken.

Zie ook