Jak zachovat odkazy a zpracovat nebo ignorovat cyklický odkaz v System.Text.Json
Tento článek ukazuje, jak zachovat odkazy a zpracovat nebo ignorovat cyklické odkazy při použití System.Text.Json k serializaci a deserializaci JSON v .NET.
Zachování odkazů a zpracování cyklických odkazů
Chcete-li zachovat odkazy a zpracovat cyklický odkaz, nastavte na Preservehodnotu ReferenceHandler . Toto nastavení způsobuje následující chování:
Při serializaci:
Při psaní složitých typů serializátor také zapisuje vlastnosti metadat (
$id
,$values
a$ref
).Při deserializaci:
Očekává se metadata (i když není povinná) a deserializátor se ji pokusí pochopit.
Použití tohoto nastavení ukazuje Preserve
následující kód.
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
Tuto funkci nelze použít k zachování typů hodnot nebo neměnných typů. Při deserializaci se vytvoří instance neměnného typu po načtení celé datové části. Proto by nebylo možné deserializovat stejnou instanci, pokud se v datové části JSON zobrazí odkaz na ni.
U hodnotových typů, neměnných typů a polí není serializována žádná referenční metadata. Při deserializaci je vyvolána výjimka, pokud $ref
nebo $id
je nalezena. Typy hodnot však ignorují $id
(a $values
v případě kolekcí), aby bylo možné deserializovat datové části, které byly serializovány pomocí , Newtonsoft.Jsoncož serializuje metadata pro tyto typy.
Chcete-li určit, zda jsou objekty stejné, System.Text.Json používá ReferenceEqualityComparer.Instancepři porovnávání dvou instancí objektů rovnost odkazu (Object.ReferenceEquals(Object, Object)) místo rovnosti hodnot (Object.Equals(Object)).
Další informace o tom, jak jsou odkazy serializovány a deserializovány, naleznete v tématu ReferenceHandler.Preserve.
Třída ReferenceResolver definuje chování zachování odkazů na serializaci a deserializaci. Vytvořte odvozenou třídu pro určení vlastního chování. Příklad najdete v tématu GuidReferenceResolver.
Zachování referenčních metadat napříč více voláními serializace a deserializace
Ve výchozím nastavení se referenční data ukládají pouze do mezipaměti pro každé volání nebo Serialize Deserialize. Chcete-li zachovat odkazy z jednoho Serialize
nebo Deserialize
volání do jiného, kořen instance ReferenceResolver v lokalitě Serialize
/Deserialize
volání . Následující kód ukazuje příklad pro tento scénář:
- Máte seznam
Employee
objektů a musíte serializovat každý z nich jednotlivě. - Chcete využít odkazy uložené v překladače pro
ReferenceHandler
.
Tady je Employee
třída:
public class Employee
{
public string? Name { get; set; }
public Employee? Manager { get; set; }
public List<Employee>? DirectReports { get; set; }
}
Třída odvozená od ReferenceResolver úložišť odkazů ve slovníku:
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;
}
}
Třída, která je odvozena z ReferenceHandler blokování instance MyReferenceResolver
a vytváří novou instanci pouze v případě potřeby (v metodě pojmenované Reset
v tomto příkladu):
class MyReferenceHandler : ReferenceHandler
{
public MyReferenceHandler() => Reset();
private ReferenceResolver? _rootedResolver;
public override ReferenceResolver CreateResolver() => _rootedResolver!;
public void Reset() => _rootedResolver = new MyReferenceResolver();
}
Když vzorový kód volá serializátor, používá JsonSerializerOptions instanci, ve které ReferenceHandler je vlastnost nastavena na instanci MyReferenceHandler
. Když budete postupovat podle tohoto vzoru, nezapomeňte po dokončení serializace obnovit ReferenceResolver
slovník, aby se nezvětšil navždy.
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();
Ignorování cyklických odkazů
Místo zpracování cyklických odkazů je můžete ignorovat. Chcete-li ignorovat cyklický odkaz, nastavte ReferenceHandler hodnotu IgnoreCycles. Serializátor nastaví vlastnosti cyklický odkaz , null
jak je znázorněno v následujícím příkladu:
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
V předchozím příkladu je pod Adrian King
serializován tak, Manager
aby null
se zabránilo cyklický odkaz. Toto chování má následující výhody ReferenceHandler.Preserve:
- Zmenší velikost datové části.
- Vytvoří JSON, který je srozumitelný pro serializátory jiné než System.Text.Json a Newtonsoft.Json.
Toto chování má následující nevýhody:
- Bezobslužná ztráta dat.
- Data nemůžou provést zpáteční cestu z JSON zpět do zdrojového objektu.