Megosztás a következőn keresztül:


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.

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 JsonIncludePrivateFieldsAttributealapé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ú SecretHoldertulajdonsá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.

Lásd még