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 King
null
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.