Share via


Cara mempertahankan referensi dan menangani atau mengabaikan referensi melingkar di System.Text.Json

Pelajari cara mempertahankan referensi dan menangani atau mengabaikan referensi melingkar saat menggunakan System.Text.Json untuk membuat serial dan deserialize JSON di .NET

Mempertahankan referensi dan menangani referensi melingkar

Untuk mempertahankan referensi dan menangani referensi melingkar, atur ReferenceHandler ke Preserve. Pengaturan ini menyebabkan perilaku berikut:

  • Saat diserialisasikan:

    Saat menulis jenis kompleks, serializer juga menulis properti metadata ($id, $values, dan $ref).

  • Saat dideserialisasikan:

    Metadata diharapkan (meskipun tidak wajib), dan deserializer mencoba memahaminya.

Kode berikut mengilustrasikan penggunaan Preserve pengaturan.

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

Fitur ini tidak dapat digunakan untuk mempertahankan jenis nilai atau jenis yang tidak dapat diubah. Saat dideserialisasikan, instans jenis yang tidak dapat diubah dibuat setelah seluruh payload dibaca. Jadi tidak mungkin untuk mendeserialisasikan instans yang sama jika referensi ke instans tersebut muncul dalam payload JSON.

Untuk jenis nilai, jenis yang tidak dapat diubah, dan array, tidak ada metadata referensi yang diserialisasikan. Saat dideserialisasikan, pengecualian dimunculkan jika $ref atau $id ditemukan. Namun, jenis nilai mengabaikan $id (dan $values dalam kasus koleksi) untuk memungkinkan deserialisasi payload yang diserialisasikan dengan menggunakan Newtonsoft.Json. Newtonsoft.Json melakukan serialisasi metadata untuk jenis tersebut.

Untuk menentukan apakah objeknya sama, System.Text.Json gunakan ReferenceEqualityComparer.Instance, yang menggunakan kesetaraan referensi (Object.ReferenceEquals(Object, Object)) bukan kesetaraan nilai (Object.Equals(Object)) saat membandingkan dua instans objek.

Untuk informasi selengkapnya tentang bagaimana referensi diserialisasikan dan dideserialisasikan, lihat ReferenceHandler.Preserve.

Kelas ReferenceResolver menentukan perilaku mempertahankan referensi pada serialisasi dan deserialisasi. Buat kelas turunan untuk menentukan perilaku kustom. Sebagai contoh, lihat GuidReferenceResolver.

Mempertahankan metadata referensi di beberapa panggilan serialisasi dan deserialisasi

Secara default, data referensi hanya di-cache untuk setiap panggilan ke Serialize atau Deserialize. Untuk mempertahankan referensi dari satu panggilan Serialize/Deserialize ke panggilan lain, akar instans ReferenceResolver di situs panggilan Serialize/Deserialize. Kode berikut menunjukkan contoh untuk skenario ini:

  • Anda memiliki daftar Employee objek dan Anda harus menserialisasikan masing-masing satu per satu.
  • Anda ingin memanfaatkan referensi yang disimpan di resolver untuk ReferenceHandler.

Berikut Employee kelasnya:

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

Kelas yang berasal dari ReferenceResolver menyimpan referensi dalam kamus:

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

Kelas yang berasal dari ReferenceHandler menyimpan instans MyReferenceResolver dan membuat instans baru hanya jika diperlukan (dalam metode bernama Reset dalam contoh ini):

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

Saat kode sampel memanggil pembuat serialisasi, kode tersebut menggunakan instans JsonSerializerOptions di mana properti ReferenceHandler diatur ke instans MyReferenceHandler. Jika Anda mengikuti pola ini, pastikan untuk mengatur ulang kamus ReferenceResolver saat Anda selesai melakukan serialisasi, agar tidak terus berkembang.

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

Abaikan referensi melingkar

Alih-alih menangani referensi melingkar, Anda dapat mengabaikannya. Mengabaikan referensi melingkar, atur ReferenceHandler ke IgnoreCycles. Serializer mengatur properti referensi melingkar ke null, seperti yang ditunjukkan dalam contoh berikut:

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

Dalam contoh sebelumnya, Manager di bagian diserialisasikan Adrian Kingnull untuk menghindari referensi melingkar. Perilaku ini memiliki keuntungan sebagai berikut ReferenceHandler.Preserve:

  • Ini mengurangi ukuran payload.
  • Ini menciptakan JSON yang dapat dipahami untuk serializer selain System.Text.Json dan Newtonsoft.Json.

Perilaku ini memiliki keuntungan sebagai berikut:

  • Kehilangan data secara diam-diam.
  • Data tidak dapat melakukan perjalanan pulang pergi dari JSON kembali ke objek sumber.

Lihat juga