JSON sözleşmelerini özelleştirme

Kitaplık, System.Text.Json her .NET türü için bir JSON sözleşmesi oluşturur ve bu, türün nasıl seri hale getirileceği ve seri durumdan çıkarılacağı anlamına gelen bir JSON sözleşmesi oluşturur. Sözleşme, türün şeklinden türetilir ve bu, özellikleri ve alanları ile IEnumerable veya IDictionary arabirimlerinden birini uygulayıp uygulamadığı gibi karakteristikleri içerir. Türler, çalışma zamanında yansıma kullanılarak veya kaynak oluşturucu kullanılarak derleme zamanında sözleşmelere eşlenir.

.NET 7'den başlayarak, türlerin JSON'a nasıl dönüştürüldüğü üzerinde daha fazla denetim sağlamak için bu JSON sözleşmelerini özelleştirebilirsiniz. Aşağıdaki listede, seri hale getirme ve seri durumdan çıkarma için yapabileceğiniz özelleştirme türlerinin yalnızca bazı örnekleri gösterilmektedir:

  • Özel alanları ve özellikleri seri hale getirme.
  • Tek bir özellik için birden çok adı destekleyin (örneğin, önceki bir kitaplık sürümü farklı bir ad kullandıysa).
  • Belirli bir ad, tür veya değere sahip özellikleri yoksayın.
  • Açık null değerleri ve JSON yükündeki değer eksikliğini ayırt edin.
  • System.Runtime.Serialization öznitelikleri destekleyin, örneğin DataContractAttribute. Daha fazla bilgi için bkz . System.Runtime.Serialization öznitelikleri.
  • JSON, hedef türün parçası olmayan bir özellik içeriyorsa bir özel durum oluşturun. Daha fazla bilgi için bkz Eksik üyeleri işleme.

Nasıl katılım sağlanır?

Özelleştirmeye bağlanmanın iki yolu vardır. Her ikisi de, işi serileştirilmesi gereken her tür için bir örnek sağlamak olan bir JsonTypeInfo çözümleyici elde etmeyi içerir.

  • DefaultJsonTypeInfoResolver() oluşturucusunu çağırarak JsonSerializerOptions.TypeInfoResolver öğesini elde edin ve özel eylemlerinizi Modifiers özelliğine ekleyin.

    Örneğin:

    JsonSerializerOptions options = new()
    {
        TypeInfoResolver = new DefaultJsonTypeInfoResolver
        {
            Modifiers =
            {
                MyCustomModifier1,
                MyCustomModifier2
            }
        }
    };
    

    Birden çok değiştirici eklerseniz, bunlar sırayla çağrılır.

  • Özel bir çözümleyici yazıp IJsonTypeInfoResolver uygularak.

    • Eğer bir tür işlenmiyorsa, IJsonTypeInfoResolver.GetTypeInfo o tür için null döndürmelidir.
    • Özel çözümleyicinizi, örneğin varsayılan çözümleyiciyle diğer çözümleyicilerle de birleştirebilirsiniz. Çözümleyiciler, tür için null JsonTypeInfo olmayan bir değer döndürülene kadar sırayla sorgulanacaktır.

Yapılandırılabilir özellikler

özelliği, JsonTypeInfo.Kind dönüştürücülerin belirli bir türü (örneğin, bir nesne veya dizi olarak) nasıl serileştirdiğini ve özelliklerinin serileştirilip serileştirilmediğini gösterir. Bir türün JSON sözleşmesinin hangi yönlerini yapılandırabileceğinizi belirlemek için bu özelliği sorgulayabilirsiniz. Dört farklı tür vardır:

JsonTypeInfo.Kind Açıklama
JsonTypeInfoKind.Object Dönüştürücü türü bir JSON nesnesine serileştirir ve özelliklerini kullanır. Bu tür çoğu sınıf ve yapı türü için kullanılır ve en fazla esnekliği sağlar.
JsonTypeInfoKind.Enumerable Dönüştürücü, türü bir JSON dizisine seri hale getirir. Bu türler, List<T> ve dizi gibi türler için kullanılır.
JsonTypeInfoKind.Dictionary Dönüştürücü, türü JSON nesnesi olarak seri hale getirecektir. Bu tür, Dictionary<K, V> gibi türler için kullanılır.
JsonTypeInfoKind.None Dönüştürücü, türü nasıl serileştireceğini veya hangi JsonTypeInfo özellikleri kullanacağını belirtmez. Bu tür, , System.Objectve intgibi stringtürler ve özel dönüştürücü kullanan tüm türler için kullanılır.

Değiştiriciler

Değiştirici, sözleşmenin geçerli durumunu bağımsız değişken olarak alan ve sözleşmeye değişiklikler yapan bir JsonTypeInfo parametresine sahip bir yöntem veya Action<JsonTypeInfo>'dir. Örneğin, ilgilendiğiniz özelliği bulmak için belirtilen JsonTypeInfo üzerindeki önceden doldurulmuş özellikleri yineleyebilirsiniz ve ardından JsonPropertyInfo.Get özelliğini (serileştirme için) veya JsonPropertyInfo.Set özelliğini (seri durumdan çıkarma için) değiştirebilirsiniz. İsterseniz JsonTypeInfo.CreateJsonPropertyInfo(Type, String) kullanarak yeni bir özellik oluşturabilir ve bunu JsonTypeInfo.Properties koleksiyona ekleyebilirsiniz.

Aşağıdaki tabloda, yapabileceğiniz değişiklikler ve bunların nasıl elde edilebileceği gösterilmektedir.

Değişiklik Uygulanabilir JsonTypeInfo.Kind Bunu başarma Örnek
Özelliğin değerini özelleştirme JsonTypeInfoKind.Object Özellik için JsonPropertyInfo.Get temsilcisini (serileştirme) veya JsonPropertyInfo.Set temsilcisini (seri durumdan çıkarma) değiştirin. Özelliğin değerini artırma
Özellik ekleme veya kaldırma JsonTypeInfoKind.Object Listeye öğe ekleyin veya listeden JsonTypeInfo.Properties öğe kaldırın. Özel alanları seri hale getirme
Bir özelliği koşullu olarak seri hale getirme JsonTypeInfoKind.Object Özelliğin JsonPropertyInfo.ShouldSerialize koşulunu değiştirin. Belirli bir türe sahip özellikleri yoksayın
Belirli bir tür için sayı işlemeyi özelleştirme JsonTypeInfoKind.None Türün JsonTypeInfo.NumberHandling değerini değiştirin. Int değerlerinin dizeler olmasını sağla

Örnek: Özelliğin değerini artırma

Değiştiricinin JsonPropertyInfo.Set temsilcisini değiştirerek serileştirme işleminde belirli bir özelliğin değerini artırdığı aşağıdaki örneği göz önünde bulundurun. Değiştiriciyi tanımlamanın yanı sıra örnek, değeri artırılması gereken özelliği bulmak için kullandığı yeni bir özniteliği de tanıtır. Bu, bir özelliği özelleştirme örneğidir.

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
        }
    }
}

Çıktıda, Product örneği her seri durumdan çıkarıldığında RoundTrips değerinin arttırıldığını dikkat edin.

Örnek: Özel alanları seri hale getirme

Varsayılan olarak, System.Text.Json özel alanları ve özellikleri yoksayar. Bu örnek, bu varsayılanı değiştirmek için yeni bir sınıf genelinde öznitelik, JsonIncludePrivateFieldsAttribute, ekler. Değiştirici bir türdeki özniteliği bulursa, türündeki tüm özel alanları yeni özellikler olarak öğesine JsonTypeInfoekler.

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]
        }
    }
}

İpucu

Özel alan adlarınız alt çizgilerle başlıyorsa, alanları yeni JSON özellikleri olarak eklerken adlardan alt çizgi kaldırmayı göz önünde bulundurun.

Örnek: Belirli bir türe sahip özellikleri göz ardı etme

Modelinizin kullanıcılara göstermek istemediğiniz belirli adlara veya türlere sahip özellikleri olabilir. Örneğin, kimlik bilgilerini veya yükte bulundurulması gereksiz olan bazı bilgileri depolayan bir özelliğiniz olabilir.

Aşağıdaki örnekte, belirli bir türdeki SecretHolderözelliklerin nasıl filtreleneceği gösterilmektedir. Bunu, belirtilen türe IList<T> sahip özellikleri listeden kaldırmak için bir JsonTypeInfo.Properties uzantı yöntemi kullanarak yapar. Filtrelenen özellikler sözleşmeden tamamen kaybolur, yani System.Text.Json serileştirme veya seri durumdan çıkarma sırasında bunlara bakmaz.

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--);
                }
            }
        }
    }
}

Örnek: Int değerlerinin dizeler olması için izin ver

Giriş JSON'unuz sayısal türlerden birinin çevresinde tırnak işaretleri içerebilir ancak diğerlerinde içermeyebilir. Sınıf üzerinde denetiminiz olsaydı, türüne JsonNumberHandlingAttribute yerleştirerek bunu düzeltebilirdiniz, ancak kontrol sizde değil. .NET 7'den önce, bu davranışı düzeltmek için bir özel dönüştürücü yazmanız gerekirdi ve bu da oldukça fazla kod yazmayı gerektirir. Sözleşme özelleştirmesini kullanarak, herhangi bir tür için sayı işleme davranışını özelleştirebilirsiniz.

Aşağıdaki örnek tüm int değerlerin davranışını değiştirir. Örnek, herhangi bir türe veya herhangi bir türün belirli bir özelliğine uygulanacak şekilde kolayca ayarlanabilir.

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)
        }
    }
}

Bir dizeden değerlerin okunmasına int izin veren değiştirici olmadan, program bir özel durumla sona ererdi:

İşlenmeyen özel durum. System.Text.Json.JsonException: JSON değeri System.Int32'ye dönüştürülemedi. Yol: $.X | SatırNumarası: 0 | SatırdakiBaytKonumu: 9.

Serileştirmeyi özelleştirmenin diğer yolları

Bir sözleşmeyi özelleştirmenin yanı sıra serileştirme ve seri durumdan çıkarma davranışını etkilemenin aşağıdakiler de dahil olmak üzere başka yolları da vardır:

  • JsonAttribute'den türetilen öznitelikleri kullanarak, örneğin JsonIgnoreAttribute ve JsonPropertyOrderAttribute.
  • Örneğin, bir adlandırma ilkesi ayarlamak veya sayı yerine dize olarak enumeration değerlerini seri hale getirmek için JsonSerializerOptions öğesini değiştirerek.
  • JSON yazma ve seri durumdan çıkarma sırasında bir nesne oluşturma işleminin gerçek işini yerine getiren özel bir dönüştürücü yazarak.

Öznitelik eklemek için türüne erişiminiz olmayabilir, çünkü sözleşme özelleştirmesi bu önceden var olan özelleştirmelere göre bir geliştirmedir. Buna ek olarak, özel bir dönüştürücü yazmak karmaşıktır ve performansa zarar verir.

Ayrıca bkz.