Asp.net 核心 WebApi 中空值的自定义序列化

Custom serialization of null values in Asp.net core WebApi

我必须按照以下规则更改对象的默认 json serialization/deserialization:

  1. 当 C# 对象为 null 时,它必须序列化为 ID 等于 0 的 json 对象。
  2. 当 json 对象的 id 等于 0 时,它必须反序列化为空值的 C# 对象。

我试试这个:

public class EntityConverter : JsonConverter<EventDefinition>
{
    public override EventDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        EventDefinition result = JsonSerializer.Deserialize<EventDefinition>(ref reader, options);
        if (result.EventDefinitionId == 0)
            return null;
        else return result;
    }

    public override void Write(Utf8JsonWriter writer, EventDefinition value, JsonSerializerOptions options)
    {
        if (value == null)
        {
            value = new EventDefinition();
            value.EventDefinitionId = 0;
            writer.WriteStringValue(JsonSerializer.Serialize(value));
        }
        else
            writer.WriteStringValue(JsonSerializer.Serialize(value));
    }
}

我需要替换 writer.WriteStringValue 因为它将整个对象写为字符串,我需要的是在修改后继续对象的正常序列化。我怎样才能做到这一点?

.NET 5 允许自定义转换器处理 null(如果他们愿意)。来自 How to write custom converters for JSON serialization (marshalling) in .NET: Handle null values:

To enable a custom converter to handle null for a reference or value type, override JsonConverter<T>.HandleNull to return true.

因此在EntityConverter中你需要添加

public override bool HandleNull => true;

但是,当您这样做时会遇到第二个问题,即在 Write() 中,您正在将 EventDefinition 的序列化 JSON 编写为双序列化字符串值,而不是作为一个对象。如何将其序列化为对象(替换为 null )?如果您已将 EntityConverter 应用为:

  • A [JsonConverter] 应用于 属性.
  • 已将转换器添加到 Converters 集合中。

然后您可以增强 from 以包含 HandleNull,如下所示:

public class EntityConverter : DefaultConverterFactory<EventDefinition>
{
    protected override bool HandleNull => true;
    
    protected override EventDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions, JsonConverter<EventDefinition> defaultConverter)
    {
        var result = base.Read(ref reader, typeToConvert, modifiedOptions, defaultConverter);
        return result?.EventDefinitionId == 0 ? null : result;
    }
    
    protected override void Write(Utf8JsonWriter writer, EventDefinition value, JsonSerializerOptions modifiedOptions, JsonConverter<EventDefinition> defaultConverter) 
    {
        value ??= new EventDefinition { EventDefinitionId = 0 };
        base.Write(writer, value, modifiedOptions, defaultConverter);
    }
}

public abstract class DefaultConverterFactory<T> : JsonConverterFactory
{
    class NullHandlingDefaultConverter : DefaultConverter
    {
        public NullHandlingDefaultConverter(JsonSerializerOptions options, DefaultConverterFactory<T> factory) : base(options, factory) { }
        public override bool HandleNull => true;
    }
    
    class DefaultConverter : JsonConverter<T>
    {
        readonly JsonSerializerOptions modifiedOptions;
        readonly DefaultConverterFactory<T> factory;
        readonly JsonConverter<T> defaultConverter;

        public DefaultConverter(JsonSerializerOptions options, DefaultConverterFactory<T> factory)
        {
            this.factory = factory ?? throw new ArgumentNullException();
            this.modifiedOptions = options.CopyAndRemoveConverter(factory.GetType());
            this.defaultConverter = (JsonConverter<T>)modifiedOptions.GetConverter(typeof(T));
        }

        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => factory.Write(writer, value, modifiedOptions, defaultConverter);

        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => factory.Read(ref reader, typeToConvert, modifiedOptions, defaultConverter);
    }

    protected virtual T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions, JsonConverter<T> defaultConverter)
        => defaultConverter.ReadOrSerialize<T>(ref reader, typeToConvert, modifiedOptions);

    protected virtual void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions modifiedOptions, JsonConverter<T> defaultConverter) 
        => defaultConverter.WriteOrSerialize(writer, value, modifiedOptions);

    protected virtual bool HandleNull => false;

    public override bool CanConvert(Type typeToConvert) => typeof(T) == typeToConvert;

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => HandleNull ? new NullHandlingDefaultConverter(options, this) : new DefaultConverter(options, this);
}

public static class JsonSerializerExtensions
{
    public static JsonSerializerOptions CopyAndRemoveConverter(this JsonSerializerOptions options, Type converterType)
    {
        var copy = new JsonSerializerOptions(options);
        for (var i = copy.Converters.Count - 1; i >= 0; i--)
            if (copy.Converters[i].GetType() == converterType)
                copy.Converters.RemoveAt(i);
        return copy;
    }

    public static void WriteOrSerialize<T>(this JsonConverter<T> converter, Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        if (converter != null)
            converter.Write(writer, value, options);
        else
            JsonSerializer.Serialize(writer, value, options);
    }

    public static T ReadOrSerialize<T>(this JsonConverter<T> converter, ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (converter != null)
            return converter.Read(ref reader, typeToConvert, options);
        else
            return (T)JsonSerializer.Deserialize(ref reader, typeToConvert, options);
    }
}

并序列化一个List<EventDefinition> events如下:

var options = new JsonSerializerOptions
{
    Converters = { new EntityConverter() },
    WriteIndented = true, // If you want
};

var json = JsonSerializer.Serialize(events, options);

备注:

  • 在 .NET Core 中 3.x HandleNull 不可用JsonConverter<T>.Write() will never be passed a null value in that version. Thus, in that version you would need to adopt a different approach, such as adding a custom converter for the containing type(s) or serializing DTOs 而不是“真实”对象。

    Json.NET也绝不会叫唤其JsonConverter.WriteJson() for a null value so the the two serializers are consistent in that limitation in 3.x, see for confirmation. The answer to that question shows a workaround using a custom contract resolver, so reverting to Json.NET might be an option for you in 3.1. System.Text.Json in contrast .

  • 如果您使用 JsonConverterAttributeEntityConverter 直接应用到 EventDefinition,即:

    [JsonConverter(typeof(EntityConverter))]
    public class EventDefinition
    {
        public int EventDefinitionId { get; set; }
    }
    

    那么上面的方法就不行了。事实上,似乎没有办法为直接应用转换器的类型的实例生成“正常”序列化。相反,您需要在 Write()Read() 中手动写入和读取每个必需的 属性,或者编写您自己的反射代码来自动执行此操作。

演示 fiddle here.