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ı ve veya IDictionary arabirimini IEnumerable uygulayıp uygulamadığı gibi özellikleri 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 gibi DataContractAttributedestek öznitelikleri. 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.

Kabul etme

Ö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.

  • öğesini almak için oluşturucuyu DefaultJsonTypeInfoResolver() çağırarak ve özel eylemlerinizi özelliğine Modifiers ekleyerek.JsonSerializerOptions.TypeInfoResolver

    Örneğin:

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

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

  • uygulayan IJsonTypeInfoResolverözel bir çözümleyici yazarak.

    • Bir tür işlenmediyse, IJsonTypeInfoResolver.GetTypeInfo bu tür için döndürülmelidir null .
    • Özel çözümleyicinizi, örneğin varsayılan çözümleyici gibi diğer kişilerle de birleştirebilirsiniz. Çözümleyiciler, türü için null JsonTypeInfo olmayan bir değer döndürülene kadar sırasıyla sorgulanı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ür ve dizisi gibi List<T> türler için kullanılır.
JsonTypeInfoKind.Dictionary Dönüştürücü türü bir JSON nesnesine seri hale getirecektir. Bu tür gibi Dictionary<K, V>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, , intve stringgibi System.Objecttü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şmede değişiklikler yapan bir veya parametresine sahip JsonTypeInfo bir yöntemdirAction<JsonTypeInfo>. Örneğin, ilgilendiğiniz özelliği bulmak için belirtilen JsonTypeInfo üzerindeki önceden doldurulmuş özellikleri yineleyip özelliğini (serileştirme için) veya JsonPropertyInfo.Set özelliğini (seri durumdan çıkarma için) değiştirebilirsinizJsonPropertyInfo.Get. İsterseniz kullanarak JsonTypeInfo.CreateJsonPropertyInfo(Type, String) yeni bir özellik oluşturabilir ve bunu koleksiyona JsonTypeInfo.Properties 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 özelliği için temsilciyi JsonPropertyInfo.Get (serileştirme için) veya JsonPropertyInfo.Set temsilciyi (seri durumdan çıkarma için) 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 yoksayma
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 temsilcisini değiştirerek JsonPropertyInfo.Set seri durumdan çıkarmada 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, örneğin seri durumdan çıkarıldığında değerinin RoundTrips artırıldığına Product dikkat edin.

Örnek: Özel alanları seri hale getirme

Varsayılan olarak, System.Text.Json özel alanları ve özellikleri yoksayar. Bu örnek, JsonIncludePrivateFieldsAttributebu varsayılanı değiştirmek için yeni bir sınıf genelinde özniteliği 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 yoksayma

Modelinizin kullanıcılara göstermek istemediğiniz belirli adlara veya türlere sahip özellikleri olabilir. Örneğin, kimlik bilgilerini veya yükte yer almayan 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 JsonTypeInfo.Properties sahip özellikleri listeden kaldırmak için bir IList<T> 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 varsa, bunu düzeltmek için türüne yer JsonNumberHandlingAttribute verirseniz, ancak bunu yapamazsınız. .NET 7'dan önce, bu davranışı düzeltmek için bir özel dönüştürücü yazmanız gerekir ve bu da adil bir kod parçası 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 | LineNumber: 0 | BytePositionInLine: 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:

  • örneğin, ve JsonPropertyOrderAttribute'den JsonAttributeJsonIgnoreAttribute türetilen öznitelikleri kullanarak.
  • Örneğin, bir adlandırma ilkesi ayarlamak veya numaralandırma değerlerini sayı yerine dize olarak seri hale getirmek için öğesini değiştirerek JsonSerializerOptions.
  • 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.