Самое простое решение было бы для return null!
:
#nullable enable
internal class JsonFalseOrObjectConverter<T> : JsonConverter<T> where T : class
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.False)
{
return null!;
}
else
{
return JsonSerializer.Deserialize<T>(ref reader);
}
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options){}
}
Ссылочные классы имеют значение , допускающие обнуление, поэтому компилятор просто использует T
, когда встречается T?
.
Лучшим вариантом было бы создать что-то похожее на типа опции F #, которое содержит значение Some, если значение установлено, None, если значение равно false. Делая это Option
структурой, мы получаем значение по умолчанию None
, даже если свойство отсутствует или равно нулю:
readonly struct Option<T>
{
public readonly T Value {get;}
public readonly bool IsSome {get;}
public readonly bool IsNone =>!IsSome;
public Option(T value)=>(Value,IsSome)=(value,true);
public void Deconstruct(out T value)=>(value)=(Value);
}
//Convenience methods, similar to F#'s Option module
static class Option
{
public static Option<T> Some<T>(T value)=>new Option<T>(value);
public static Option<T> None<T>()=>default;
}
Десериализатор может возвращать None()
или default
, если встречается false:
internal class JsonFalseOrObjectConverter<T> : JsonConverter<Option<T>> where T : class
{
public override Option<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.False)
{
return Option.None<T>(); // or default
}
else
{
return Option.Some(JsonSerializer.Deserialize<T>(ref reader));
}
}
public override void Write(Utf8JsonWriter writer, Option<T> value, JsonSerializerOptions options)
{
switch (value)
{
case Option<T> (_ ,false) :
JsonSerializer.Serialize(writer,false,options);
break;
case Option<T> (var v,true) :
JsonSerializer.Serialize(writer,v,options);
break;
}
}
}
Метод Write
показывает, как можно обработать Option<T>
с помощью сопоставления с образцом.
Используя этот сериализатор, следующие классы:
class Category
{
public string Name{get;set;}
}
class Product
{
public string Name{get;set;}
public Option<Category> Category {get;set;}
}
Можно сериализовать с помощью false
, созданного для отсутствующих категорий:
var serializerOptions = new JsonSerializerOptions
{
Converters = { new JsonFalseOrObjectConverter<Category>() }
};
var product1=new Product{Name="A"};
var json=JsonSerializer.Serialize(product1,serializerOptions);
Возвращает:
{"Name":"A","Category":false}
При десериализации этой строки возвращается Product
, Category
которого равен Option<Category>
без значения:
var product2=JsonSerializer.Deserialize<Product>(json,serializerOptions);
Debug.Assert(product2.Category.IsNone);
Выражения сопоставления с образцом можно использовать для извлечения и использования свойств категории, еслиимеет значение, например:
string category=product2.Category switch { Option<Category> (_ ,false) =>"No Category",
Option<Category> (var v,true) => v.Name};
или
if(product2.Category is Option<Category>(var cat,true))
{
Console.WriteLine(cat.Name);
}