JSON-szerződés testreszabása
A System.Text.Json kódtár minden .NET-típushoz létrehoz egy JSON-szerződést, amely meghatározza a típus szerializálásának és deszerializálásának módját. A szerződés a típus alakjából származik, amely olyan jellemzőket tartalmaz, mint például a tulajdonságai és a mezői, és hogy megvalósítja-e az interfészt vagy IDictionary az interfésztIEnumerable. A típusok le vannak képezve a szerződésekre futáskor a tükrözés vagy a forrásgenerátor használatával történő fordításkor.
A .NET 7-től kezdve testre szabhatja ezeket a JSON-szerződéseket, hogy jobban szabályozhassa a típusok JSON-lá alakításának módját, és fordítva. Az alábbi lista csak néhány példát mutat be a szerializáláshoz és deszerializáláshoz használható testreszabások típusaira:
- Privát mezők és tulajdonságok szerializálása.
- Egyetlen tulajdonság több nevének támogatása (például ha egy korábbi kódtárverzió más nevet használt).
- Adott névvel, típussal vagy értékkel rendelkező tulajdonságok figyelmen kívül hagyása.
- Különbséget kell tenni az explicit
null
értékek és az értékek hiánya között a JSON hasznos adatai között. - Támogatási System.Runtime.Serialization attribútumok, például DataContractAttribute. További információ: System.Runtime.Szerializálás attribútumok.
- Kivételt képez, ha a JSON tartalmaz egy olyan tulajdonságot, amely nem része a céltípusnak. További információ: Hiányzó tagok kezelése.
A bejelentkezés menete
A testreszabáshoz kétféleképpen lehet csatlakozni. Mindkettő magában foglalja egy feloldó beszerzését, amelynek feladata, hogy minden szerializálandó típushoz biztosítson példányt JsonTypeInfo .
A konstruktor meghívásával DefaultJsonTypeInfoResolver() szerezze be és adja hozzá az JsonSerializerOptions.TypeInfoResolveregyéni műveleteket a tulajdonságához Modifiers .
Példa:
JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { MyCustomModifier1, MyCustomModifier2 } } };
Ha több módosító karaktert ad hozzá, a rendszer egymás után hívja meg őket.
Egyéni feloldó megírásával, amely implementálja a IJsonTypeInfoResolverelemet.
- Ha nem kezeli a típust, IJsonTypeInfoResolver.GetTypeInfo az adott típushoz kell visszatérnie
null
. - Az egyéni feloldót másokkal is kombinálhatja, például az alapértelmezett feloldóval. A feloldókat a rendszer mindaddig lekérdezi, amíg a típushoz nem null JsonTypeInfo értéket ad vissza.
- Ha nem kezeli a típust, IJsonTypeInfoResolver.GetTypeInfo az adott típushoz kell visszatérnie
Konfigurálható szempontok
A JsonTypeInfo.Kind tulajdonság azt jelzi, hogy a konverter hogyan szerializál egy adott típust , például objektumként vagy tömbként, és hogy a tulajdonságai szerializálva vannak-e. A tulajdonság lekérdezésével meghatározhatja, hogy egy típus JSON-szerződésének mely aspektusait konfigurálhatja. Négy különböző típus létezik:
JsonTypeInfo.Kind |
Leírás |
---|---|
JsonTypeInfoKind.Object | A konverter JSON-objektummá szerializálja a típust, és annak tulajdonságait használja. Ez a típus a legtöbb osztály- és struktúratípushoz használatos, és a legnagyobb rugalmasságot teszi lehetővé. |
JsonTypeInfoKind.Enumerable | A konverter JSON-tömbbe szerializálja a típust. Ez a típus a hasonló List<T> és tömb típusúakhoz használatos. |
JsonTypeInfoKind.Dictionary | A konverter JSON-objektummá szerializálja a típust. Ez a típus az olyan típusok esetében használatos, mint a Dictionary<K, V> . |
JsonTypeInfoKind.None | A konverter nem határozza meg, hogyan szerializálja a típust vagy a JsonTypeInfo használni kívánt tulajdonságokat. Ez a típus az olyan típusok esetében használatos, mint a System.Object, int és string , és minden olyan típushoz, amely egyéni konvertert használ. |
Módosítók
A módosító egy Action<JsonTypeInfo>
vagy olyan paraméterrel rendelkező JsonTypeInfo metódus, amely argumentumként lekéri a szerződés aktuális állapotát, és módosítja a szerződést. Például a megadott JsonTypeInfo előre feltöltött tulajdonságokon át is haladva megkeresheti a kívánt tulajdonságot, majd módosíthatja annak tulajdonságát (szerializálás esetén) vagy JsonPropertyInfo.Set tulajdonságát JsonPropertyInfo.Get (deszerializálás esetén). Létrehozhat egy új tulajdonságot JsonTypeInfo.CreateJsonPropertyInfo(Type, String) is, és felveheti azt a JsonTypeInfo.Properties gyűjteménybe.
Az alábbi táblázat azokat a módosításokat mutatja be, hogy milyen módosításokat hajthat végre, és hogyan érheti el őket.
Módosítás | Alkalmazandó JsonTypeInfo.Kind |
Ennek elérése | Példa |
---|---|---|---|
Tulajdonság értékének testreszabása | JsonTypeInfoKind.Object |
Módosítsa a JsonPropertyInfo.Get delegáltat (szerializáláshoz) vagy JsonPropertyInfo.Set delegáltat (deszerializáláshoz) a tulajdonsághoz. | Tulajdonság értékének növelése |
Tulajdonságok hozzáadása vagy eltávolítása | JsonTypeInfoKind.Object |
Elemek hozzáadása vagy eltávolítása a JsonTypeInfo.Properties listából. | Privát mezők szerializálása |
Tulajdonság feltételes szerializálása | JsonTypeInfoKind.Object |
Módosítsa a JsonPropertyInfo.ShouldSerialize tulajdonság predikátumát. | Adott típusú tulajdonságok figyelmen kívül hagyása |
Számkezelés testreszabása egy adott típushoz | JsonTypeInfoKind.None |
Módosítsa a JsonTypeInfo.NumberHandling típus értékét. | A int értékek sztringekként való használatának engedélyezése |
Példa: Tulajdonság értékének növelése
Vegye figyelembe az alábbi példát, amikor a módosító a deszerializálás egy bizonyos tulajdonságának értékét növeli a delegált módosításával JsonPropertyInfo.Set . A módosító definiálása mellett a példa egy új attribútumot is bevezet, amellyel megkeresi azt a tulajdonságot, amelynek értékét növelni kell. Ez egy tulajdonság testreszabására szolgáló példa.
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
}
}
}
Figyelje meg a kimenetben, hogy az érték RoundTrips
minden alkalommal növekszik, amikor a Product
példány deszerializálva van.
Példa: Magánmezők szerializálása
Alapértelmezés szerint figyelmen kívül hagyja a System.Text.Json
privát mezőket és tulajdonságokat. Ez a példa egy új osztályszintű attribútumot ad hozzá az JsonIncludePrivateFieldsAttribute
alapértelmezett beállítás módosításához. Ha a módosító megkeresi az attribútumot egy típuson, a típus összes magánmezője új tulajdonságként lesz hozzáadva a típushoz 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]
}
}
}
Tipp.
Ha a privát mezőnevek aláhúzásjelekkel kezdődnek, érdemes lehet eltávolítani az aláhúzásjeleket a nevekből, amikor új JSON-tulajdonságként adja hozzá a mezőket.
Példa: Adott típusú tulajdonságok figyelmen kívül hagyása
Előfordulhat, hogy a modell olyan tulajdonságokkal rendelkezik, amelyek neve vagy típusa nem érhető el a felhasználók számára. Előfordulhat például, hogy olyan tulajdonsága van, amely hitelesítő adatokat tárol, vagy olyan információkat, amelyek feleslegesek a hasznos adatokban.
Az alábbi példa bemutatja, hogyan szűrheti ki az adott típusú SecretHolder
tulajdonságokat. Ezt egy IList<T> bővítménymetódus használatával teszi lehetővé, hogy eltávolítson minden olyan tulajdonságot, amely rendelkezik a megadott típussal a JsonTypeInfo.Properties listából. A szűrt tulajdonságok teljesen eltűnnek a szerződésből, ami azt jelenti System.Text.Json
, hogy sem szerializálás, sem deszerializálás során nem tekinti meg őket.
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--);
}
}
}
}
}
Példa: A int értékek sztringekként való használatának engedélyezése
Lehet, hogy a bemeneti JSON idézőjeleket tartalmazhat az egyik numerikus típus körül, másokon azonban nem. Ha szabályozta volna az osztályt, a típusra helyezhetné JsonNumberHandlingAttribute a javítást, de nem. A .NET 7 előtt meg kell írnia egy egyéni konvertert ennek a viselkedésnek a javításához, amelyhez tisztességes kódrészletet kell írnia. A szerződés testreszabásával bármilyen típushoz testre szabhatja a számkezelési viselkedést.
Az alábbi példa az összes int
érték viselkedését módosítja. A példa egyszerűen módosítható úgy, hogy bármilyen típusra vagy bármilyen típusú adott tulajdonságra vonatkozzanak.
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)
}
}
}
A sztring értékeinek olvasását int
engedélyező módosító nélkül a program kivétellel véget ért volna:
Kezeletlen kivétel. System.Text.Json.JsonException: A JSON-érték nem konvertálható System.Int32 értékre. Elérési út: $. X | LineNumber: 0 | BytePositionInLine: 9.
A szerializálás testreszabásának egyéb módjai
A szerződés testreszabása mellett más módokon is befolyásolható a szerializálás és a deszerializálás viselkedése, beleértve a következőket:
- Például JsonIgnoreAttributeJsonPropertyOrderAttributea következőből JsonAttributeszármaztatott attribútumok használatával: .
- Ha például módosít JsonSerializerOptionsegy elnevezési szabályzatot, vagy szám helyett sztringekként szerializálja az enumerálási értékeket.
- Egyéni konverter írásával, amely elvégzi a JSON írásának tényleges munkáját, és a deszerializálás során létrehoz egy objektumot.
A szerződés testreszabása a meglévő testreszabásokkal szemben javulást jelent, mivel előfordulhat, hogy nem fér hozzá a típushoz attribútumok hozzáadásához, és az egyéni konverter írása összetett, és rontja a teljesítményt.