.NET'te JSON serileştirme (marshalling) için özel dönüştürücüler yazma
Bu makalede, ad alanında System.Text.Json sağlanan JSON serileştirme sınıfları için özel dönüştürücülerin nasıl oluşturulacağı gösterilmektedir. giriş için System.Text.Json
bkz . .NET'te JSON'ı seri hale getirme ve seri durumdan çıkarma.
Dönüştürücü, bir nesneyi veya değeri JSON'a ve JSON'dan dönüştüren bir sınıftır. Ad System.Text.Json
alanında, JavaScript ilkelleriyle eşlenen çoğu temel tür için yerleşik dönüştürücüler bulunur. Yerleşik dönüştürücülerin varsayılan davranışını geçersiz kılmak için özel dönüştürücüler yazabilirsiniz. Örneğin:
- Değerlerin aa/gg/yyyy biçiminde gösterilmesini isteyebilirsiniz
DateTime
. Varsayılan olarak, RFC 3339 profili de dahil olmak üzere ISO 8601-1:2019 desteklenir. Daha fazla bilgi için, içindeki DateTime ve DateTimeOffset desteğine System.Text.Jsonbakın. - PoCO'ları JSON dizesi olarak, örneğin bir
PhoneNumber
türle seri hale getirmek isteyebilirsiniz.
Ayrıca, yeni işlevlerle özelleştirmek veya genişletmek System.Text.Json
için özel dönüştürücüler yazabilirsiniz. Bu makalenin devamında aşağıdaki senaryolar ele alınmıştır:
- Çıkarsanan türleri nesne özelliklerine seri durumdan çıkarma.
- Polimorfik seri durumdan çıkarma desteği.
- Türler için
Stack
gidiş dönüş desteği. - Varsayılan sistem dönüştürücüsü kullanın.
- Çıkarsanan türleri nesne özelliklerine seri durumdan çıkarma.
- Polimorfik seri durumdan çıkarma desteği.
- Türler için
Stack
gidiş dönüş desteği. - Numaralandırma dizesi seri durumdan çıkarma için adlandırma ilkeleri.
Visual Basic, özel dönüştürücüler yazmak için kullanılamaz, ancak C# kitaplıklarında uygulanan dönüştürücüleri çağırabilir. Daha fazla bilgi için bkz . Visual Basic desteği.
Özel dönüştürücü desenleri
Özel dönüştürücü oluşturmak için iki desen vardır: temel desen ve fabrika deseni. Fabrika düzeni, tür veya açık genel türleri Enum
işleyen dönüştürücüler içindir. Temel desen, genel olmayan ve kapalı genel türler içindir. Örneğin, aşağıdaki türler için dönüştürücüler fabrika desenini gerektirir:
Temel desen tarafından işlenebilen bazı tür örnekleri şunlardır:
Temel desen, bir türü işleyebilen bir sınıf oluşturur. Fabrika düzeni, çalışma zamanında hangi türün gerekli olduğunu belirleyen ve uygun dönüştürücüleri dinamik olarak oluşturan bir sınıf oluşturur.
Örnek temel dönüştürücü
Aşağıdaki örnek, mevcut bir veri türü için varsayılan serileştirmeyi geçersiz kılan bir dönüştürücüdür. Dönüştürücü, özellikler için DateTimeOffset
aa/gg/yy biçimini kullanır.
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SystemTextJsonSamples
{
public class DateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset>
{
public override DateTimeOffset Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
DateTimeOffset.ParseExact(reader.GetString()!,
"MM/dd/yyyy", CultureInfo.InvariantCulture);
public override void Write(
Utf8JsonWriter writer,
DateTimeOffset dateTimeValue,
JsonSerializerOptions options) =>
writer.WriteStringValue(dateTimeValue.ToString(
"MM/dd/yyyy", CultureInfo.InvariantCulture));
}
}
Örnek fabrika desen dönüştürücüsü
Aşağıdaki kod ile Dictionary<Enum,TValue>
çalışan özel bir dönüştürücü gösterir. İlk genel tür parametresi Enum
ve ikincisi açık olduğundan kod fabrika desenini izler. CanConvert
yöntemi yalnızca Dictionary
ilki bir tür olan iki genel parametresi olan için Enum
döndürürtrue
. İç dönüştürücü, için çalışma zamanında sağlanan türü işlemek için TValue
mevcut bir dönüştürücü alır.
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SystemTextJsonSamples
{
public class DictionaryTKeyEnumTValueConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType)
{
return false;
}
if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>))
{
return false;
}
return typeToConvert.GetGenericArguments()[0].IsEnum;
}
public override JsonConverter CreateConverter(
Type type,
JsonSerializerOptions options)
{
Type[] typeArguments = type.GetGenericArguments();
Type keyType = typeArguments[0];
Type valueType = typeArguments[1];
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
[keyType, valueType]),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: [options],
culture: null)!;
return converter;
}
private class DictionaryEnumConverterInner<TKey, TValue> :
JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
{
private readonly JsonConverter<TValue> _valueConverter;
private readonly Type _keyType;
private readonly Type _valueType;
public DictionaryEnumConverterInner(JsonSerializerOptions options)
{
// For performance, use the existing converter.
_valueConverter = (JsonConverter<TValue>)options
.GetConverter(typeof(TValue));
// Cache the key and value types.
_keyType = typeof(TKey);
_valueType = typeof(TValue);
}
public override Dictionary<TKey, TValue> Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
var dictionary = new Dictionary<TKey, TValue>();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return dictionary;
}
// Get the key.
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string? propertyName = reader.GetString();
// For performance, parse with ignoreCase:false first.
if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
!Enum.TryParse(propertyName, ignoreCase: true, out key))
{
throw new JsonException(
$"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
}
// Get the value.
reader.Read();
TValue value = _valueConverter.Read(ref reader, _valueType, options)!;
// Add to dictionary.
dictionary.Add(key, value);
}
throw new JsonException();
}
public override void Write(
Utf8JsonWriter writer,
Dictionary<TKey, TValue> dictionary,
JsonSerializerOptions options)
{
writer.WriteStartObject();
foreach ((TKey key, TValue value) in dictionary)
{
string propertyName = key.ToString();
writer.WritePropertyName
(options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);
_valueConverter.Write(writer, value, options);
}
writer.WriteEndObject();
}
}
}
}
Temel deseni izleme adımları
Aşağıdaki adımlarda, temel deseni izleyerek dönüştürücü oluşturma adımları açıklanmaktadır:
- burada serileştirilecek ve seri durumdan çıkarılacak tür olan bir JsonConverter<T>
T
sınıf oluşturun. Read
Gelen JSON'un seri durumdan çıkarılıp türüneT
dönüştürmek için yöntemini geçersiz kılın. Utf8JsonReader JSON okumak için yöntemine geçirilen öğesini kullanın. Seri hale getirici geçerli JSON kapsamı için tüm verileri geçirdiğinden kısmi verileri işleme konusunda endişelenmeniz gerekmez. Bu nedenle, öğesinin döndürdüğünütrue
Read doğrulamak için veya TrySkip çağrısı Skip yapmak gerekmez.- türündeki
Write
T
gelen nesnesini seri hale getirmek için yöntemini geçersiz kılın. Utf8JsonWriter JSON yazmak için yöntemine geçirilen öğesini kullanın. CanConvert
Yalnızca gerekirse yöntemini geçersiz kılın. Varsayılan uygulama, dönüştürülecek tür türündeT
olduğunda döndürürtrue
. Bu nedenle, yalnızca türüT
destekleyen dönüştürücülerin bu yöntemi geçersiz kılması gerekmez. Bu yöntemi geçersiz kılması gereken bir dönüştürücü örneği için bu makalenin devamında yer alan polimorfik seri durumdan çıkarma bölümüne bakın.
Özel dönüştürücüler yazmak için başvuru uygulamaları olarak yerleşik dönüştürücüler kaynak koduna başvurabilirsiniz.
Fabrika desenini izleme adımları
Aşağıdaki adımlarda fabrika desenini izleyerek dönüştürücü oluşturma adımları açıklanmaktadır:
- öğesinden JsonConverterFactorytüretilen bir sınıf oluşturun.
CanConvert
Dönüştürülecek tür dönüştürücü tarafından işlenebilir olduğunda döndürülecektrue
yöntemi geçersiz kılın. Örneğin, dönüştürücü içinseList<T>
, yalnızca ,List<string>
veList<DateTime>
işleyebilirList<int>
.CreateConverter
Çalışma zamanında sağlanan tür dönüştürme işlemini işleyecek bir dönüştürücü sınıfının örneğini döndürmek için yöntemini geçersiz kılın.- Yöntemin örnek oluşturduğu
CreateConverter
dönüştürücü sınıfını oluşturun.
Bir nesneyi dizeye ve dizeden dönüştürme kodu tüm türler için aynı olmadığından, açık genel türler için fabrika düzeni gereklidir. Açık bir genel tür için dönüştürücü (List<T>
örneğin), arka planda kapalı bir genel tür (List<DateTime>
örneğin) için bir dönüştürücü oluşturması gerekir. Dönüştürücü tarafından işlenebilen her kapalı genel türü işlemek için kod yazılmalıdır.
Tür Enum
, açık bir genel türe benzer: için Enum
dönüştürücü, arka planda belirli Enum
bir (WeekdaysEnum
örneğin) için bir dönüştürücü oluşturması gerekir.
yönteminde Read
kullanımı Utf8JsonReader
Dönüştürücünüz bir JSON nesnesini dönüştürüyorsa, Utf8JsonReader
yöntemi başladığında begin nesnesi belirtecinde Read
konumlandırılır. Daha sonra bu nesnedeki tüm belirteçleri okumanız ve okuyucunun ilgili uç nesne belirtecinde konumlandırılmış şekilde yöntemden çıkmanız gerekir. Nesnenin sonunun ötesini okursanız veya karşılık gelen uç belirteci ulaşmadan önce durdurursanız şunu belirten bir JsonException
özel durum alırsınız:
'ConverterName' dönüştürücüsü çok fazla okuyor veya yeterli değil.
Bir örnek için önceki fabrika deseni örnek dönüştürücüsne bakın. yöntemi, Read
okuyucunun bir başlangıç nesnesi belirtecinde konumlandırıldığını doğrulayarak başlar. Sonraki uç nesne belirtecinde konumlandırıldığını bulana kadar okur. Nesnenin içindeki bir nesneyi gösterecek bir araya gelen başlatma nesnesi belirteci olmadığından sonraki uç nesne belirtecinde durur. Bir diziyi dönüştürüyorsanız başlangıç belirteci ve bitiş belirteci ile ilgili aynı kural geçerlidir. Örnek için bu makalenin devamında yer alan Stack<T>
örnek dönüştürücüye bakın.
Hata işleme
Seri hale getirici özel durum türleri JsonException ve NotSupportedExceptioniçin özel işleme sağlar.
JsonException
İleti olmadan bir JsonException
oluşturursanız, seri hale getirici hataya neden olan JSON bölümünün yolunu içeren bir ileti oluşturur. Örneğin, deyimi throw new JsonException()
aşağıdaki örneğe benzer bir hata iletisi oluşturur:
Unhandled exception. System.Text.Json.JsonException:
The JSON value could not be converted to System.Object.
Path: $.Date | LineNumber: 1 | BytePositionInLine: 37.
Bir ileti (örneğin, ) sağlarsanız, throw new JsonException("Error occurred")
seri hale getirici yine de , LineNumberve BytePositionInLine özelliklerini ayarlarPath.
Notsupportedexception
oluşturursanız NotSupportedException
, her zaman iletideki yol bilgilerini alırsınız. Bir ileti sağlarsanız, yol bilgileri iletiye eklenir. Örneğin, deyimi throw new NotSupportedException("Error occurred.")
aşağıdaki örneğe benzer bir hata iletisi oluşturur:
Error occurred. The unsupported member type is located on type
'System.Collections.Generic.Dictionary`2[Samples.SummaryWords,System.Int32]'.
Path: $.TemperatureRanges | LineNumber: 4 | BytePositionInLine: 24
Hangi özel durum türü ne zaman oluşturulur?
JSON yükü seri durumdan çıkarılmakta olan tür için geçerli olmayan belirteçler içerdiğinde bir JsonException
oluşturun.
Belirli türlere izin vermek istemediğinizde, bir NotSupportedException
atabilirsiniz. Bu özel durum, seri hale getiricinin desteklenmeyen türler için otomatik olarak oluşturduğunu gösterir. Örneğin, System.Type
güvenlik nedeniyle desteklenmez, bu nedenle seri durumdan çıkarma girişimi bir NotSupportedException
ile sonuçlanır.
Gerektiğinde başka özel durumlar da oluşturabilirsiniz, ancak bunlar otomatik olarak JSON yol bilgilerini içermez.
Özel dönüştürücü kaydetme
ve Deserialize
yöntemlerinin Serialize
kullanmasını sağlamak için özel bir dönüştürücü kaydedin. Aşağıdaki yaklaşımlardan birini seçin:
- Koleksiyona dönüştürücü sınıfının bir örneğini JsonSerializerOptions.Converters ekleyin.
- Özel dönüştürücü gerektiren özelliklere [JsonConverter] özniteliğini uygulayın.
- [JsonConverter] özniteliğini bir sınıfa veya özel bir değer türünü temsil eden bir yapıya uygulayın.
Kayıt örneği - Dönüştürücüler koleksiyonu
DateTimeOffsetJsonConverter'ın türündeki özellikler için varsayılan olmasını sağlayan bir örnek aşağıda verilmiştirDateTimeOffset:
var serializeOptions = new JsonSerializerOptions
{
WriteIndented = true,
Converters =
{
new DateTimeOffsetJsonConverter()
}
};
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);
Aşağıdaki türdeki bir örneği seri hale getirdiğinizden şu şekilde düşünebilirsiniz:
public class WeatherForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
Özel dönüştürücüsünün kullanıldığını gösteren bir JSON çıktısı örneği aşağıda verilmişti:
{
"Date": "08/01/2019",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
Aşağıdaki kod, özel DateTimeOffset
dönüştürücü kullanarak seri durumdan çıkarmak için aynı yaklaşımı kullanır:
var deserializeOptions = new JsonSerializerOptions();
deserializeOptions.Converters.Add(new DateTimeOffsetJsonConverter());
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions)!;
Bir özellik üzerinde kayıt örneği - [JsonConverter]
Aşağıdaki kod özelliği için Date
özel bir dönüştürücü seçer:
public class WeatherForecastWithConverterAttribute
{
[JsonConverter(typeof(DateTimeOffsetJsonConverter))]
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
Seri hale WeatherForecastWithConverterAttribute
getirmek için kullanılacak kodun kullanılması JsonSerializeOptions.Converters
gerekmez:
var serializeOptions = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);
Seri durumdan çıkarma kodu için de kullanılması Converters
gerekmez:
weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithConverterAttribute>(jsonString)!;
Kayıt örneği - bir tür üzerinde [JsonConverter]
Bir yapı oluşturan ve özniteliğini [JsonConverter]
uygulayan kod aşağıdadır:
using System.Text.Json.Serialization;
namespace SystemTextJsonSamples
{
[JsonConverter(typeof(TemperatureConverter))]
public struct Temperature
{
public Temperature(int degrees, bool celsius)
{
Degrees = degrees;
IsCelsius = celsius;
}
public int Degrees { get; }
public bool IsCelsius { get; }
public bool IsFahrenheit => !IsCelsius;
public override string ToString() =>
$"{Degrees}{(IsCelsius ? "C" : "F")}";
public static Temperature Parse(string input)
{
int degrees = int.Parse(input.Substring(0, input.Length - 1));
bool celsius = input.Substring(input.Length - 1) == "C";
return new Temperature(degrees, celsius);
}
}
}
Yukarıdaki yapı için özel dönüştürücü aşağıdadır:
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SystemTextJsonSamples
{
public class TemperatureConverter : JsonConverter<Temperature>
{
public override Temperature Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
Temperature.Parse(reader.GetString()!);
public override void Write(
Utf8JsonWriter writer,
Temperature temperature,
JsonSerializerOptions options) =>
writer.WriteStringValue(temperature.ToString());
}
}
[JsonConverter]
yapısındaki özniteliği, türündeki Temperature
özellikler için varsayılan olarak özel dönüştürücü kaydeder. Dönüştürücü, seri hale getirdiğinizde veya seri durumdan çıkardığınızda aşağıdaki türün özelliğinde TemperatureCelsius
otomatik olarak kullanılır:
public class WeatherForecastWithTemperatureStruct
{
public DateTimeOffset Date { get; set; }
public Temperature TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
Dönüştürücü kaydı önceliği
Serileştirme veya seri durumdan çıkarma sırasında, her JSON öğesi için en yüksek önceliğe ve en düşük önceliğe kadar listelenen aşağıdaki sırayla bir dönüştürücü seçilir:
[JsonConverter]
bir özelliğe uygulanır.- Koleksiyona
Converters
bir dönüştürücü eklendi. [JsonConverter]
özel bir değer türüne veya POCO'ya uygulanır.
Koleksiyonda Converters
bir tür için birden çok özel dönüştürücü kayıtlıysa, döndüren true
CanConvert
ilk dönüştürücü kullanılır.
Yerleşik dönüştürücü yalnızca geçerli bir özel dönüştürücü kaydedilmediyse seçilir.
Yaygın senaryolar için dönüştürücü örnekleri
Aşağıdaki bölümlerde, yerleşik işlevselliğin işlemediği bazı yaygın senaryoları ele alan dönüştürücü örnekleri sağlanır.
- Çıkarsanan türleri nesne özelliklerine seri durumdan çıkarma.
- Türler için
Stack
gidiş dönüş desteği. - Varsayılan sistem dönüştürücüsü kullanın.
- Çıkarsanan türleri nesne özelliklerine seri durumdan çıkarma.
- Polimorfik seri durumdan çıkarma desteği.
- Türler için
Stack
gidiş dönüş desteği. - Numaralandırma dizesi seri durumdan çıkarma için adlandırma ilkeleri.
Örnek DataTable dönüştürücü için bkz . Desteklenen koleksiyon türleri.
Çıkarsanan türleri nesne özelliklerine seri durumdan çıkarma
türünde object
bir özelliğe seri durumdan çıkarılırken bir JsonElement
nesne oluşturulur. Bunun nedeni, seri durumdan çıkarıcının hangi CLR türünü oluşturacağını bilmemesi ve tahmin etmeye çalışmamasıdır. Örneğin, bir JSON özelliğinde "true" varsa, seri durumdan çıkarıcı değerin bir Boolean
olduğunu çıkarmaz ve bir öğenin "01/01/2019" değeri varsa seri durumdan çıkarıcı bunun bir DateTime
olduğunu çıkarmaz.
Tür çıkarımı yanlış olabilir. Seri durumdan çıkarıcı ondalık ayırıcısı olmayan bir JSON sayısını olarak long
ayrıştırıyorsa, değer ilk olarak veya ulong
BigInteger
olarak serileştirilmişse aralık dışı sorunlara neden olabilir. Ondalık ayırıcısı double
olan bir sayıyı olarak ayrıştırma, sayı başlangıçta olarak decimal
serileştirilmişse duyarlığı kaybedebilir.
Tür çıkarımı gerektiren senaryolar için, aşağıdaki kod özellikler için object
özel bir dönüştürücü gösterir. Kod şu işlemleri dönüştürür:
true
vefalse
Boolean
- Ondalık olmayan sayılar
long
- Ondalık içeren sayılar:
double
- Tarihler:
DateTime
- Dizeler:
string
- Diğer her şey
JsonElement
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CustomConverterInferredTypesToObject
{
public class ObjectToInferredTypesConverter : JsonConverter<object>
{
public override object Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) => reader.TokenType switch
{
JsonTokenType.True => true,
JsonTokenType.False => false,
JsonTokenType.Number when reader.TryGetInt64(out long l) => l,
JsonTokenType.Number => reader.GetDouble(),
JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime,
JsonTokenType.String => reader.GetString()!,
_ => JsonDocument.ParseValue(ref reader).RootElement.Clone()
};
public override void Write(
Utf8JsonWriter writer,
object objectToWrite,
JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options);
}
public class WeatherForecast
{
public object? Date { get; set; }
public object? TemperatureCelsius { get; set; }
public object? Summary { get; set; }
}
public class Program
{
public static void Main()
{
string jsonString = """
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
""";
WeatherForecast weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString)!;
Console.WriteLine($"Type of Date property no converter = {weatherForecast.Date!.GetType()}");
var options = new JsonSerializerOptions();
options.WriteIndented = true;
options.Converters.Add(new ObjectToInferredTypesConverter());
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, options)!;
Console.WriteLine($"Type of Date property with converter = {weatherForecast.Date!.GetType()}");
Console.WriteLine(JsonSerializer.Serialize(weatherForecast, options));
}
}
}
// Produces output like the following example:
//
//Type of Date property no converter = System.Text.Json.JsonElement
//Type of Date property with converter = System.DateTime
//{
// "Date": "2019-08-01T00:00:00-07:00",
// "TemperatureCelsius": 25,
// "Summary": "Hot"
//}
Örnekte dönüştürücü kodu ve özellikleri olan object
bir WeatherForecast
sınıf gösterilmektedir. Main
yöntemi, bir JSON dizesini önce dönüştürücü kullanmadan ve sonra dönüştürücüsü kullanmadan bir WeatherForecast
örneğe seri durumdan çıkartır. Konsol çıkışı, dönüştürücü olmadan özelliğin Date
JsonElement
çalışma zamanı türünün ; dönüştürücü ile çalışma zamanı türünün olduğunu DateTime
gösterir.
Ad alanı içindeki birim testleri klasöründe, özelliklere System.Text.Json.Serialization
seri durumdan çıkarma object
işlemini işleyen özel dönüştürücülere daha fazla örnek bulunur.
Polimorfik seri durumdan çıkarma desteği
.NET 7, hem polimorfik serileştirme hem de seri durumdan çıkarma desteği sağlar. Ancak, önceki .NET sürümlerinde sınırlı polimorfik serileştirme desteği vardı ve seri durumdan çıkarma desteği yoktu. .NET 6 veya önceki bir sürümü kullanıyorsanız seri durumdan çıkarma için özel bir dönüştürücü gerekir.
Örneğin, ve Customer
türetilmiş sınıflarla Employee
soyut bir Person
temel sınıfınız olduğunu varsayalım. Polimorfik seri durumdan çıkarma, tasarım zamanında seri durumdan çıkarma hedefi olarak belirtebileceğiniz Person
ve Customer
Employee
JSON'daki nesnelerin çalışma zamanında doğru seri durumdan çıkarıldığı anlamına gelir. Seri durumdan çıkarma sırasında, JSON'da gerekli türü tanımlayan ipuçları bulmanız gerekir. Kullanılabilir ipucu türleri her senaryoya göre farklılık gösterir. Örneğin, ayrımcı özelliği kullanılabilir olabilir veya belirli bir özelliğin varlığına veya yokluğuna güvenmeniz gerekebilir. geçerli sürümü System.Text.Json
, çok biçimli seri durumdan çıkarma senaryolarının nasıl işleneceğini belirten öznitelikler sağlamaz, bu nedenle özel dönüştürücüler gereklidir.
Aşağıdaki kodda bir temel sınıf, iki türetilmiş sınıf ve bunlar için özel bir dönüştürücü gösterilmektedir. Dönüştürücü, polimorfik seri durumdan çıkarma yapmak için ayrıştırıcı özelliği kullanır. Tür ayrımcısı sınıf tanımlarında değil, serileştirme sırasında oluşturulur ve seri durumdan çıkarma sırasında okunur.
Önemli
Örnek kod, JSON nesne adı/değer çiftlerinin sıralı kalmasını gerektirir; bu JSON'un standart bir gereksinimi değildir.
public class Person
{
public string? Name { get; set; }
}
public class Customer : Person
{
public decimal CreditLimit { get; set; }
}
public class Employee : Person
{
public string? OfficeNumber { get; set; }
}
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SystemTextJsonSamples
{
public class PersonConverterWithTypeDiscriminator : JsonConverter<Person>
{
enum TypeDiscriminator
{
Customer = 1,
Employee = 2
}
public override bool CanConvert(Type typeToConvert) =>
typeof(Person).IsAssignableFrom(typeToConvert);
public override Person Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string? propertyName = reader.GetString();
if (propertyName != "TypeDiscriminator")
{
throw new JsonException();
}
reader.Read();
if (reader.TokenType != JsonTokenType.Number)
{
throw new JsonException();
}
TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();
Person person = typeDiscriminator switch
{
TypeDiscriminator.Customer => new Customer(),
TypeDiscriminator.Employee => new Employee(),
_ => throw new JsonException()
};
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return person;
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "CreditLimit":
decimal creditLimit = reader.GetDecimal();
((Customer)person).CreditLimit = creditLimit;
break;
case "OfficeNumber":
string? officeNumber = reader.GetString();
((Employee)person).OfficeNumber = officeNumber;
break;
case "Name":
string? name = reader.GetString();
person.Name = name;
break;
}
}
}
throw new JsonException();
}
public override void Write(
Utf8JsonWriter writer, Person person, JsonSerializerOptions options)
{
writer.WriteStartObject();
if (person is Customer customer)
{
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.Customer);
writer.WriteNumber("CreditLimit", customer.CreditLimit);
}
else if (person is Employee employee)
{
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.Employee);
writer.WriteString("OfficeNumber", employee.OfficeNumber);
}
writer.WriteString("Name", person.Name);
writer.WriteEndObject();
}
}
}
Aşağıdaki kod dönüştürücüye kaydeder:
var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new PersonConverterWithTypeDiscriminator());
Dönüştürücü, seri hale getirmek için aynı dönüştürücü kullanılarak oluşturulan JSON'un seri durumdan çıkarılabilir, örneğin:
[
{
"TypeDiscriminator": 1,
"CreditLimit": 10000,
"Name": "John"
},
{
"TypeDiscriminator": 2,
"OfficeNumber": "555-1234",
"Name": "Nancy"
}
]
Önceki örnekteki dönüştürücü kodu her özelliği el ile okur ve yazar. Bunun alternatifi, işin bir kısmını aramak Deserialize
veya Serialize
yapmaktır. Bir örnek için bu StackOverflow gönderisini inceleyin.
Polimorfik seri durumdan çıkarmanın alternatif bir yolu
yöntemini çağırabilirsiniz Deserialize
Read
:
- Örneğin bir kopyasını
Utf8JsonReader
oluşturun.Utf8JsonReader
Bir yapı olduğundan, bunun için yalnızca bir atama deyimi gerekir. - Ayırıcı belirteçleri okumak için kopyayı kullanın.
- İhtiyacınız olan türü bildiğinizde özgün
Reader
örneği kullanarak arayınDeserialize
. ÖzgünReader
örnek hala begin nesne belirtecini okuyacak şekilde konumlandırıldığından çağırabilirsinizDeserialize
.
Bu yöntemin bir dezavantajı, dönüştürücüye kaydeden özgün seçenekler örneğini geçirememenizdir Deserialize
. Bunun yapılması, Gerekli özellikler bölümünde açıklandığı gibi yığın taşmasına neden olur. Aşağıdaki örnekte bu alternatifi kullanan bir Read
yöntem gösterilmektedir:
public override Person Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string? propertyName = readerClone.GetString();
if (propertyName != "TypeDiscriminator")
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.Number)
{
throw new JsonException();
}
TypeDiscriminator typeDiscriminator = (TypeDiscriminator)readerClone.GetInt32();
Person person = typeDiscriminator switch
{
TypeDiscriminator.Customer => JsonSerializer.Deserialize<Customer>(ref reader)!,
TypeDiscriminator.Employee => JsonSerializer.Deserialize<Employee>(ref reader)!,
_ => throw new JsonException()
};
return person;
}
Türler için Stack
gidiş dönüş desteği
Bir JSON dizesini bir Stack
nesneye seri durumdan çıkartır ve sonra bu nesneyi serileştirirseniz, yığının içeriği ters sırada olur. Bu davranış, aşağıdaki türler ve arabirimler ile bunlardan türetilen kullanıcı tanımlı türler için geçerlidir:
Yığındaki özgün sırayı koruyan serileştirmeyi ve seri durumdan çıkarmayı desteklemek için özel bir dönüştürücü gereklidir.
Aşağıdaki kod, nesnelere ve nesnelerden Stack<T>
yuvarlak kopyalamayı etkinleştiren özel bir dönüştürücü gösterir:
using System.Diagnostics;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SystemTextJsonSamples
{
public class JsonConverterFactoryForStackOfT : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
=> typeToConvert.IsGenericType
&& typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>);
public override JsonConverter CreateConverter(
Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(typeToConvert.IsGenericType &&
typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>));
Type elementType = typeToConvert.GetGenericArguments()[0];
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
typeof(JsonConverterForStackOfT<>)
.MakeGenericType(new Type[] { elementType }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: null,
culture: null)!;
return converter;
}
}
public class JsonConverterForStackOfT<T> : JsonConverter<Stack<T>>
{
public override Stack<T> Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray)
{
throw new JsonException();
}
reader.Read();
var elements = new Stack<T>();
while (reader.TokenType != JsonTokenType.EndArray)
{
elements.Push(JsonSerializer.Deserialize<T>(ref reader, options)!);
reader.Read();
}
return elements;
}
public override void Write(
Utf8JsonWriter writer, Stack<T> value, JsonSerializerOptions options)
{
writer.WriteStartArray();
var reversed = new Stack<T>(value);
foreach (T item in reversed)
{
JsonSerializer.Serialize(writer, item, options);
}
writer.WriteEndArray();
}
}
}
Aşağıdaki kod dönüştürücüye kaydeder:
var options = new JsonSerializerOptions
{
Converters = { new JsonConverterFactoryForStackOfT() },
};
Numaralandırma dizesi seri durumdan çıkarma için adlandırma ilkeleri
Varsayılan olarak, yerleşik JsonStringEnumConverter sabit listeleri için dize değerlerini seri hale getirebilir ve seri durumdan çıkarabilirsiniz. Belirtilen adlandırma ilkesi olmadan veya adlandırma ilkesiyle CamelCase çalışır. Yılan örneği gibi diğer adlandırma ilkelerini desteklemez. Yılan örneği adlandırma ilkesi kullanılırken sabit listesi dizesi değerlerine gidiş dönüşlü kopyalamayı destekleyebilecek özel dönüştürücü kodu hakkında bilgi için bkz. GitHub sorunu dotnet/runtime #31619. Alternatif olarak, sabit listesi dizesi değerlerine gidiş-dönüş yaparken adlandırma ilkelerini uygulamak için yerleşik destek sağlayan .NET 7 veya sonraki sürümlerine yükseltin.
Varsayılan sistem dönüştürücüsü kullan
Bazı senaryolarda, özel bir dönüştürücüde varsayılan sistem dönüştürücüsü kullanmak isteyebilirsiniz. Bunu yapmak için, aşağıdaki örnekte gösterildiği gibi özelliğinden sistem dönüştürücüsünü JsonSerializerOptions.Default alın:
public class MyCustomConverter : JsonConverter<int>
{
private readonly static JsonConverter<int> s_defaultConverter =
(JsonConverter<int>)JsonSerializerOptions.Default.GetConverter(typeof(int));
// Custom serialization logic
public override void Write(
Utf8JsonWriter writer, int value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
// Fall back to default deserialization logic
public override int Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return s_defaultConverter.Read(ref reader, typeToConvert, options);
}
}
Null değerleri işleme
Seri hale getirici varsayılan olarak null değerleri aşağıdaki gibi işler:
Başvuru türleri ve Nullable<T> türleri için:
- Serileştirmede özel dönüştürücülere geçirilmez
null
. - Seri durumdan çıkarmada özel dönüştürücülere geçmez
JsonTokenType.Null
. - Seri durumdan çıkarmada bir
null
örnek döndürür. - Serileştirmede doğrudan yazıcıyla birlikte yazar
null
.
- Serileştirmede özel dönüştürücülere geçirilmez
Null değer atanamayan değer türleri için:
- Seri durumdan çıkarma işleminde özel dönüştürücülere geçer
JsonTokenType.Null
. (Kullanılabilir özel dönüştürücü yoksa, türü için iç dönüştürücü tarafından birJsonException
özel durum oluşturulur.)
- Seri durumdan çıkarma işleminde özel dönüştürücülere geçer
Bu null işleme davranışı öncelikle dönüştürücüye ek çağrı atlayarak performansı iyileştirmektir. Buna ek olarak, dönüştürücüleri null atanabilir türler için her Read
ve Write
yöntem geçersiz kılma işleminin başında denetlenmeye null
zorlamaktan kaçınılır.
Bir başvuru veya değer türü için işlemek null
üzere özel dönüştürücü etkinleştirmek için, aşağıdaki örnekte gösterildiği gibi döndürecek true
şekilde geçersiz kılınJsonConverter<T>.HandleNull:
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CustomConverterHandleNull
{
public class Point
{
public int X { get; set; }
public int Y { get; set; }
[JsonConverter(typeof(DescriptionConverter))]
public string? Description { get; set; }
}
public class DescriptionConverter : JsonConverter<string>
{
public override bool HandleNull => true;
public override string Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
reader.GetString() ?? "No description provided.";
public override void Write(
Utf8JsonWriter writer,
string value,
JsonSerializerOptions options) =>
writer.WriteStringValue(value);
}
public class Program
{
public static void Main()
{
string json = @"{""x"":1,""y"":2,""Description"":null}";
Point point = JsonSerializer.Deserialize<Point>(json)!;
Console.WriteLine($"Description: {point.Description}");
}
}
}
// Produces output like the following example:
//
//Description: No description provided.
Başvuruları koruma
Varsayılan olarak, başvuru verileri yalnızca veya çağrısı için önbelleğe SerializeDeserializealınır. Bir çağrıdan diğerine Serialize
/Deserialize
yapılan başvuruları kalıcı hale getirmek için, çağrısı sitesindeki örneğin kökünü/ReferenceResolverSerialize
Deserialize
oluşturun. Aşağıdaki kodda bu senaryo için bir örnek gösterilmektedir:
- Türü için
Company
özel bir dönüştürücü yazarsınız. - özelliği el ile seri hale
Supervisor
getirmek istemezsiniz. Bu birEmployee
. Bunu seri hale getiriciye devretmek ve ayrıca önceden kaydettiğiniz başvuruları korumak istiyorsunuz.
ve Company
sınıfları şunlardırEmployee
:
public class Employee
{
public string? Name { get; set; }
public Employee? Manager { get; set; }
public List<Employee>? DirectReports { get; set; }
public Company? Company { get; set; }
}
public class Company
{
public string? Name { get; set; }
public Employee? Supervisor { get; set; }
}
Dönüştürücü şöyle görünür:
class CompanyConverter : JsonConverter<Company>
{
public override Company Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, Company value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteString("Name", value.Name);
writer.WritePropertyName("Supervisor");
JsonSerializer.Serialize(writer, value.Supervisor, options);
writer.WriteEndObject();
}
}
Başvurulardan ReferenceResolver türetilen bir sınıf, başvuruları bir sözlükte depolar:
class MyReferenceResolver : ReferenceResolver
{
private uint _referenceCount;
private readonly Dictionary<string, object> _referenceIdToObjectMap = new ();
private readonly Dictionary<object, string> _objectToReferenceIdMap = new (ReferenceEqualityComparer.Instance);
public override void AddReference(string referenceId, object value)
{
if (!_referenceIdToObjectMap.TryAdd(referenceId, value))
{
throw new JsonException();
}
}
public override string GetReference(object value, out bool alreadyExists)
{
if (_objectToReferenceIdMap.TryGetValue(value, out string? referenceId))
{
alreadyExists = true;
}
else
{
_referenceCount++;
referenceId = _referenceCount.ToString();
_objectToReferenceIdMap.Add(value, referenceId);
alreadyExists = false;
}
return referenceId;
}
public override object ResolveReference(string referenceId)
{
if (!_referenceIdToObjectMap.TryGetValue(referenceId, out object? value))
{
throw new JsonException();
}
return value;
}
}
öğesinden ReferenceHandler türetilen bir sınıf, örneğini MyReferenceResolver
barındırıyor ve yalnızca gerektiğinde yeni bir örnek oluşturuyor (bu örnekte adlı Reset
bir yöntemde):
class MyReferenceHandler : ReferenceHandler
{
public MyReferenceHandler() => Reset();
private ReferenceResolver? _rootedResolver;
public override ReferenceResolver CreateResolver() => _rootedResolver!;
public void Reset() => _rootedResolver = new MyReferenceResolver();
}
Örnek kod seri hale getiriciyi çağırdığında, özelliğinin ReferenceHandler bir JsonSerializerOptions örneğine ayarlandığı bir örneği MyReferenceHandler
kullanır. Bu düzeni uyguladığınızda, serileştirmeyi bitirdiğinizde sözlüğü sıfırladığınızdan ReferenceResolver
emin olun ve sonsuza kadar büyümesini engelleyebilirsiniz.
var options = new JsonSerializerOptions();
options.Converters.Add(new CompanyConverter());
var myReferenceHandler = new MyReferenceHandler();
options.ReferenceHandler = myReferenceHandler;
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.WriteIndented = true;
string str = JsonSerializer.Serialize(tyler, options);
// Reset after serializing to avoid out of bounds memory growth in the resolver.
myReferenceHandler.Reset();
Yukarıdaki örnek yalnızca serileştirme yapar, ancak seri durumdan çıkarma için benzer bir yaklaşım benimsenebilir.
Diğer özel dönüştürücü örnekleri
Geçiş kaynağı Newtonsoft.JsonSystem.Text.Json makalesi ek özel dönüştürücü örnekleri içerir.
Kaynak kodundaki System.Text.Json.Serialization
birim testleri klasörü, aşağıdakiler gibi diğer özel dönüştürücü örneklerini içerir:
- Seri durumdan çıkarmada null değerini 0'a dönüştüren Int32 dönüştürücüsü
- Seri durumdan çıkarmada hem dize hem de sayı değerlerine izin veren Int32 dönüştürücüsü
- Sabit listesi dönüştürücüsü
- Dış verileri kabul eden Liste<T> dönüştürücüsü
- Virgülle ayrılmış sayı listesiyle çalışan Long[] dönüştürücüsü
Mevcut yerleşik dönüştürücülerin davranışını değiştiren bir dönüştürücü yapmanız gerekiyorsa, özelleştirme için bir başlangıç noktası olarak hizmet vermek üzere var olan dönüştürücüye ait kaynak kodunu alabilirsiniz.