Обнуляемость ссылочных типов в возвращаемом типе не соответствует переопределенному члену - PullRequest
1 голос
/ 02 ноября 2019

Я использую API, который возвращает JSON, где одно из его значений может быть ложным или объектом. Для этого я создал пользовательский System.Text.Json.Serialization.JsonConverter<T>.

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

. Проблема в том, что я получаю следующую ошибку компилятора: Possible null reference return. Я мог бы установить возвращаемый тип равным T?, но тогда я получил бы Nullability of reference types in return type doesn't match overridden member.

Как я могу это исправить?

Ответы [ 2 ]

1 голос
/ 05 ноября 2019

Самое простое решение было бы для 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);
}
1 голос
/ 04 ноября 2019

Вы указали, что универсальный тип является (ненулевым) T, но вы возвращаете null. Это явно не может быть действительным.

Вы должны будете заставить свой конвертер реализовать JsonConverter<T?> или использовать оператор нулевого прощения, если вам просто все равно.

internal class JsonFalseOrObjectConverter<T> : JsonConverter<T?> where T : class
{
    public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        ...
    }

    public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
    {
        ...
    }
}

или

public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.TokenType == JsonTokenType.False)
    {
        return null!;
    }
    ...
}
...