Delen via


Een JSON-contract aanpassen

De System.Text.Json bibliotheek bouwt een JSON-contractvoor 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.

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, inten , en stringvoor 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 Toepassing 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 JsonIncludePrivateFieldsAttributevoor 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, en het schrijven van een aangepast conversieprogramma complex is en de prestaties pijn doet.

Zie ook