Een JSON-contract aanpassen
De System.Text.Json bibliotheek bouwt een JSON-contract voor elk .NET-type, waarmee wordt gedefinieerd hoe het type moet worden geserialiseerd en gedeserialiseerd. Het contract is afgeleid van de shape van het type, die kenmerken bevat zoals de eigenschappen en velden en of het de IEnumerable of IDictionary interface implementeert. Typen worden toegewezen aan contracten tijdens runtime met behulp van weerspiegeling of tijdens het compileren met behulp van de brongenerator.
Vanaf .NET 7 kunt u deze JSON-contracten aanpassen om meer controle te krijgen over de wijze waarop typen worden geconverteerd naar JSON en omgekeerd. In de volgende lijst ziet u slechts enkele voorbeelden van de typen aanpassingen die u kunt aanbrengen in serialisatie en deserialisatie:
- Persoonlijke velden en eigenschappen serialiseren.
- Ondersteuning voor meerdere namen voor één eigenschap (bijvoorbeeld als een vorige bibliotheekversie een andere naam heeft gebruikt).
- Eigenschappen negeren met een specifieke naam, type of waarde.
- Onderscheid maken tussen expliciete
null
waarden en het ontbreken van een waarde in de JSON-nettolading. - Ondersteuningskenmerken System.Runtime.Serialization , zoals DataContractAttribute. Zie System.Runtime.Serialization-kenmerken voor meer informatie.
- Een uitzondering genereren als de JSON een eigenschap bevat die geen deel uitmaakt van het doeltype. Zie Ontbrekende leden verwerken voor meer informatie.
Aanmelden
Er zijn twee manieren om aanpassingen aan te passen. Beide omvatten het verkrijgen van een resolver, waarvan de taak is om een JsonTypeInfo exemplaar te bieden voor elk type dat moet worden geserialiseerd.
Door de DefaultJsonTypeInfoResolver() constructor aan te roepen om de JsonSerializerOptions.TypeInfoResolver aangepaste acties te verkrijgen en toe te voegen aan de Modifiers eigenschap.
Voorbeeld:
JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { MyCustomModifier1, MyCustomModifier2 } } };
Als u meerdere modifiers toevoegt, worden deze opeenvolgend aangeroepen.
Door een aangepaste resolver te schrijven die wordt geïmplementeerd IJsonTypeInfoResolver.
- Als een type niet wordt verwerkt, IJsonTypeInfoResolver.GetTypeInfo moet u teruggaan
null
naar dat type. - U kunt uw aangepaste resolver ook combineren met anderen, bijvoorbeeld de standaard resolver. De resolvers worden op volgorde opgevraagd totdat een niet-null-waarde JsonTypeInfo wordt geretourneerd voor het type.
- Als een type niet wordt verwerkt, IJsonTypeInfoResolver.GetTypeInfo moet u teruggaan
Configureerbare aspecten
De JsonTypeInfo.Kind eigenschap geeft aan hoe het conversieprogramma een bepaald type serialiseert, bijvoorbeeld als een object of als matrix, en of de eigenschappen ervan worden geserialiseerd. U kunt een query uitvoeren op deze eigenschap om te bepalen welke aspecten van het JSON-contract van een type u kunt configureren. Er zijn vier verschillende soorten:
JsonTypeInfo.Kind |
Beschrijving |
---|---|
JsonTypeInfoKind.Object | Het conversieprogramma serialiseert het type in een JSON-object en gebruikt de eigenschappen ervan. Dit type wordt gebruikt voor de meeste klasse- en structtypen en biedt de meeste flexibiliteit. |
JsonTypeInfoKind.Enumerable | Het conversieprogramma serialiseert het type in een JSON-matrix. Dit type wordt gebruikt voor typen zoals List<T> en matrices. |
JsonTypeInfoKind.Dictionary | Het conversieprogramma serialiseert het type in een JSON-object. Dit type wordt gebruikt voor typen zoals Dictionary<K, V> . |
JsonTypeInfoKind.None | Het conversieprogramma geeft niet op hoe het type wordt geserialiseerd of welke JsonTypeInfo eigenschappen het gaat gebruiken. Dit type wordt gebruikt voor typen zoals System.Object, int en , en string voor alle typen die een aangepast conversieprogramma gebruiken. |
Modifiers
Een wijzigingsfunctie is een Action<JsonTypeInfo>
of een methode met een JsonTypeInfo parameter die de huidige status van het contract als argument krijgt en wijzigingen aanbrengt in het contract. U kunt bijvoorbeeld de vooraf ingevulde eigenschappen van de opgegeven JsonTypeInfo herhalen om de eigenschap te vinden waarin u geïnteresseerd bent en vervolgens de eigenschap (voor serialisatie) of JsonPropertyInfo.Set eigenschap (voor deserialisatie) wijzigenJsonPropertyInfo.Get. U kunt ook een nieuwe eigenschap maken en JsonTypeInfo.CreateJsonPropertyInfo(Type, String) deze toevoegen aan de JsonTypeInfo.Properties verzameling.
In de volgende tabel ziet u de wijzigingen die u kunt aanbrengen en hoe u deze kunt bereiken.
Wijziging | Toepasbaar JsonTypeInfo.Kind |
Hoe u dit kunt bereiken | Opmerking |
---|---|---|---|
De waarde van een eigenschap aanpassen | JsonTypeInfoKind.Object |
Wijzig de JsonPropertyInfo.Get gemachtigde (voor serialisatie) of JsonPropertyInfo.Set gemachtigde (voor deserialisatie) voor de eigenschap. | De waarde van een eigenschap verhogen |
Eigenschappen toevoegen of verwijderen | JsonTypeInfoKind.Object |
Items toevoegen aan of verwijderen uit de JsonTypeInfo.Properties lijst. | Persoonlijke velden serialiseren |
Een eigenschap voorwaardelijk serialiseren | JsonTypeInfoKind.Object |
Wijzig het JsonPropertyInfo.ShouldSerialize predicaat voor de eigenschap. | Eigenschappen met een specifiek type negeren |
Nummerafhandeling voor een specifiek type aanpassen | JsonTypeInfoKind.None |
Wijzig de JsonTypeInfo.NumberHandling waarde voor het type. | Int-waarden toestaan dat tekenreeksen zijn |
Voorbeeld: De waarde van een eigenschap verhogen
Bekijk het volgende voorbeeld waarbij de wijzigingsfunctie de waarde van een bepaalde eigenschap bij deserialisatie vergroot door de gedelegeerde te JsonPropertyInfo.Set wijzigen. Naast het definiëren van de wijzigingsfunctie introduceert het voorbeeld ook een nieuw kenmerk dat wordt gebruikt om de eigenschap te vinden waarvan de waarde moet worden verhoogd. Dit is een voorbeeld van het aanpassen van een eigenschap.
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace Serialization
{
// Custom attribute to annotate the property
// we want to be incremented.
[AttributeUsage(AttributeTargets.Property)]
class SerializationCountAttribute : Attribute
{
}
// Example type to serialize and deserialize.
class Product
{
public string Name { get; set; } = "";
[SerializationCount]
public int RoundTrips { get; set; }
}
public class SerializationCountExample
{
// Custom modifier that increments the value
// of a specific property on deserialization.
static void IncrementCounterModifier(JsonTypeInfo typeInfo)
{
foreach (JsonPropertyInfo propertyInfo in typeInfo.Properties)
{
if (propertyInfo.PropertyType != typeof(int))
continue;
object[] serializationCountAttributes = propertyInfo.AttributeProvider?.GetCustomAttributes(typeof(SerializationCountAttribute), true) ?? Array.Empty<object>();
SerializationCountAttribute? attribute = serializationCountAttributes.Length == 1 ? (SerializationCountAttribute)serializationCountAttributes[0] : null;
if (attribute != null)
{
Action<object, object?>? setProperty = propertyInfo.Set;
if (setProperty is not null)
{
propertyInfo.Set = (obj, value) =>
{
if (value != null)
{
// Increment the value by 1.
value = (int)value + 1;
}
setProperty (obj, value);
};
}
}
}
}
public static void RunIt()
{
var product = new Product
{
Name = "Aquafresh"
};
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { IncrementCounterModifier }
}
};
// First serialization and deserialization.
string serialized = JsonSerializer.Serialize(product, options);
Console.WriteLine(serialized);
// {"Name":"Aquafresh","RoundTrips":0}
Product deserialized = JsonSerializer.Deserialize<Product>(serialized, options)!;
Console.WriteLine($"{deserialized.RoundTrips}");
// 1
// Second serialization and deserialization.
serialized = JsonSerializer.Serialize(deserialized, options);
Console.WriteLine(serialized);
// { "Name":"Aquafresh","RoundTrips":1}
deserialized = JsonSerializer.Deserialize<Product>(serialized, options)!;
Console.WriteLine($"{deserialized.RoundTrips}");
// 2
}
}
}
U ziet in de uitvoer dat de waarde van RoundTrips
elke keer dat het Product
exemplaar wordt gedeserialiseerd, wordt verhoogd.
Voorbeeld: Persoonlijke velden serialiseren
System.Text.Json
Standaard worden privévelden en -eigenschappen genegeerd. In dit voorbeeld wordt een nieuw kenmerk JsonIncludePrivateFieldsAttribute
voor de hele klasse toegevoegd om die standaardwaarde te wijzigen. Als met de wijzigingsfunctie het kenmerk voor een type wordt gevonden, worden alle privévelden van het type als nieuwe eigenschappen toegevoegd aan JsonTypeInfo.
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace Serialization
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class JsonIncludePrivateFieldsAttribute : Attribute { }
[JsonIncludePrivateFields]
public class Human
{
private string _name;
private int _age;
public Human()
{
// This constructor should be used only by deserializers.
_name = null!;
_age = 0;
}
public static Human Create(string name, int age)
{
Human h = new()
{
_name = name,
_age = age
};
return h;
}
[JsonIgnore]
public string Name
{
get => _name;
set => throw new NotSupportedException();
}
[JsonIgnore]
public int Age
{
get => _age;
set => throw new NotSupportedException();
}
}
public class PrivateFieldsExample
{
static void AddPrivateFieldsModifier(JsonTypeInfo jsonTypeInfo)
{
if (jsonTypeInfo.Kind != JsonTypeInfoKind.Object)
return;
if (!jsonTypeInfo.Type.IsDefined(typeof(JsonIncludePrivateFieldsAttribute), inherit: false))
return;
foreach (FieldInfo field in jsonTypeInfo.Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
{
JsonPropertyInfo jsonPropertyInfo = jsonTypeInfo.CreateJsonPropertyInfo(field.FieldType, field.Name);
jsonPropertyInfo.Get = field.GetValue;
jsonPropertyInfo.Set = field.SetValue;
jsonTypeInfo.Properties.Add(jsonPropertyInfo);
}
}
public static void RunIt()
{
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { AddPrivateFieldsModifier }
}
};
var human = Human.Create("Julius", 37);
string json = JsonSerializer.Serialize(human, options);
Console.WriteLine(json);
// {"_name":"Julius","_age":37}
Human deserializedHuman = JsonSerializer.Deserialize<Human>(json, options)!;
Console.WriteLine($"[Name={deserializedHuman.Name}; Age={deserializedHuman.Age}]");
// [Name=Julius; Age=37]
}
}
}
Tip
Als uw persoonlijke veldnamen beginnen met onderstrepingstekens, kunt u overwegen de onderstrepingstekens uit de namen te verwijderen wanneer u de velden als nieuwe JSON-eigenschappen toevoegt.
Voorbeeld: Eigenschappen negeren met een specifiek type
Mogelijk heeft uw model eigenschappen met specifieke namen of typen die u niet beschikbaar wilt maken voor gebruikers. U hebt bijvoorbeeld een eigenschap waarin referenties worden opgeslagen of informatie die nutteloos is voor de nettolading.
In het volgende voorbeeld ziet u hoe u eigenschappen filtert met een specifiek type, SecretHolder
. Dit doet u met behulp van een IList<T> extensiemethode om eigenschappen met het opgegeven type uit de JsonTypeInfo.Properties lijst te verwijderen. De gefilterde eigenschappen verdwijnen volledig uit het contract, wat betekent dat System.Text.Json
ze niet worden bekeken tijdens serialisatie of deserialisatie.
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace Serialization
{
class ExampleClass
{
public string Name { get; set; } = "";
public SecretHolder? Secret { get; set; }
}
class SecretHolder
{
public string Value { get; set; } = "";
}
class IgnorePropertiesWithType
{
private readonly Type[] _ignoredTypes;
public IgnorePropertiesWithType(params Type[] ignoredTypes)
=> _ignoredTypes = ignoredTypes;
public void ModifyTypeInfo(JsonTypeInfo ti)
{
if (ti.Kind != JsonTypeInfoKind.Object)
return;
ti.Properties.RemoveAll(prop => _ignoredTypes.Contains(prop.PropertyType));
}
}
public class IgnoreTypeExample
{
public static void RunIt()
{
var modifier = new IgnorePropertiesWithType(typeof(SecretHolder));
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { modifier.ModifyTypeInfo }
}
};
ExampleClass obj = new()
{
Name = "Password",
Secret = new SecretHolder { Value = "MySecret" }
};
string output = JsonSerializer.Serialize(obj, options);
Console.WriteLine(output);
// {"Name":"Password"}
}
}
public static class ListHelpers
{
// IList<T> implementation of List<T>.RemoveAll method.
public static void RemoveAll<T>(this IList<T> list, Predicate<T> predicate)
{
for (int i = 0; i < list.Count; i++)
{
if (predicate(list[i]))
{
list.RemoveAt(i--);
}
}
}
}
}
Voorbeeld: Int-waarden toestaan als tekenreeksen
Misschien kan uw invoer-JSON aanhalingstekens bevatten rond een van de numerieke typen, maar niet op andere. Als u controle over de klas had, kunt JsonNumberHandlingAttribute u dit probleem oplossen door het type op te lossen, maar niet. Voordat .NET 7, moet u een aangepast conversieprogramma schrijven om dit gedrag op te lossen. Hiervoor moet u een redelijk stukje code schrijven. Met contractaanpassing kunt u het gedrag van de nummerafhandeling voor elk type aanpassen.
In het volgende voorbeeld wordt het gedrag voor alle int
waarden gewijzigd. Het voorbeeld kan eenvoudig worden aangepast om toe te passen op elk type of voor een specifieke eigenschap van elk type.
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace Serialization
{
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
public class AllowIntsAsStringsExample
{
static void SetNumberHandlingModifier(JsonTypeInfo jsonTypeInfo)
{
if (jsonTypeInfo.Type == typeof(int))
{
jsonTypeInfo.NumberHandling = JsonNumberHandling.AllowReadingFromString;
}
}
public static void RunIt()
{
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { SetNumberHandlingModifier }
}
};
// Triple-quote syntax is a C# 11 feature.
Point point = JsonSerializer.Deserialize<Point>("""{"X":"12","Y":"3"}""", options)!;
Console.WriteLine($"({point.X},{point.Y})");
// (12,3)
}
}
}
Zonder de wijziging om leeswaarden int
uit een tekenreeks toe te staan, zou het programma zijn beëindigd met een uitzondering:
Onverwerkte uitzondering. System.Text.Json.JsonException: de JSON-waarde kan niet worden geconverteerd naar System.Int32. Pad: $. X | Lijnnummer: 0 | BytePositionInLine: 9.
Andere manieren om serialisatie aan te passen
Naast het aanpassen van een contract zijn er andere manieren om serialisatie- en deserialisatiegedrag te beïnvloeden, waaronder de volgende:
- Door kenmerken te gebruiken die zijn afgeleid van JsonAttributebijvoorbeeld JsonIgnoreAttribute en JsonPropertyOrderAttribute.
- JsonSerializerOptionsDoor bijvoorbeeld een naamgevingsbeleid in te stellen of opsommingswaarden te serialiseren als tekenreeksen in plaats van getallen.
- Door een aangepast conversieprogramma te schrijven dat het werkelijke werk van het schrijven van de JSON uitvoert en tijdens de deserialisatie een object maakt.
Contractaanpassing is een verbetering ten opzichte van deze bestaande aanpassingen, omdat u mogelijk geen toegang hebt tot het type om kenmerken toe te voegen. Daarnaast is het schrijven van een aangepast conversieprogramma complex en doet dit de prestaties pijn.