简体   繁体   English

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

[英]Custom serialization of null values in Asp.net core WebApi

I have to change the default json serialization/deserialization of an object following these rules:我必须按照以下规则更改 object 的默认 json 序列化/反序列化:

  1. When the C# object is null, it has to be serialize to json object with Id equals 0. When the C# object is null, it has to be serialize to json object with Id equals 0.
  2. When the json object has id equals 0, it has to be deserialize to C# object with null value. When the json object has id equals 0, it has to be deserialize to C# object with null value.

I try this:我试试这个:

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

I need to replace writer.WriteStringValue beacuse it writes the whole object as a string and what I need is to continue with the normal serialization of the object after the modification.我需要替换writer.WriteStringValue因为它将整个 object 写为字符串,我需要在修改后继续正常序列化 object。 How can I achieved this?我怎样才能做到这一点?

.NET 5 allows custom converters to handle null if they choose. .NET 5允许自定义转换器处理null如果他们选择)。 From How to write custom converters for JSON serialization (marshalling) in .NET: Handle null values :如何在 .NET 中为 JSON 序列化(编组)编写自定义转换器:处理 null 值

To enable a custom converter to handle null for a reference or value type, override JsonConverter<T>.HandleNull to return true.要使自定义转换器能够处理 null 以获取引用或值类型,请覆盖JsonConverter<T>.HandleNull以返回 true。

Thus in EntityConverter you need to add因此在EntityConverter你需要添加

public override bool HandleNull => true;

However, when you do you will encounter a second problem, namely that, in Write() , you are writing the serialized JSON for EventDefinition as a double-serialized string value, rather than as an object.但是,当您这样做时,您会遇到第二个问题,即在Write()中,您正在将 EventDefinition 的序列化EventDefinition为双序列化字符串值,而不是 object。 How can it be serialized as an object (with null replaced) instead?如何将其序列化为 object(替换null )? If you have applied EntityConverter as either:如果您已将EntityConverter应用为:

  • A [JsonConverter] applied to a property.应用于属性的[JsonConverter]
  • A converter added to the Converters collection.添加到Converters集合的转换器。

Then you can enhance the answer from How to use default serialization in a custom System.Text.Json JsonConverter?然后,您可以增强如何在自定义 System.Text.Json JsonConverter 中使用默认序列化的答案 to include HandleNull as follows:包括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);
    }
}

And serialize a List<EventDefinition> events as follow:并按如下方式序列化List<EventDefinition> events

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

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

Notes:笔记:

  • In .NET Core 3.x HandleNull is not available ;在 .NET Core 3.x HandleNull中不可用 JsonConverter<T>.Write() will never be passed a null value in that version. JsonConverter<T>.Write()在该版本中永远不会传递 null 值。 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 instead of "real" objects.因此,在该版本中,您需要采用不同的方法,例如为包含类型添加自定义转换器或序列化DTO而不是“真实”对象。

    Json.NET also will never call call its JsonConverter.WriteJson() for a null value so the the two serializers are consistent in that limitation in 3.x, see How to force JsonConverter.WriteJson() to be called for a null value for confirmation. Json.NET also will never call call its JsonConverter.WriteJson() for a null value so the the two serializers are consistent in that limitation in 3.x, see How to force JsonConverter.WriteJson() to be called for a null value 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.该问题的答案显示了使用自定义合同解析器的解决方法,因此在 3.1 中恢复到 Json.NET 可能是您的一个选项。 System.Text.Json in contrast does not make its contract model public .相比之下,System.Text.Json并未公开其合约 model

  • If you have applied EntityConverter directly to EventDefinition using JsonConverterAttribute , ie:如果您已使用JsonConverterAttributeEntityConverter直接应用于EventDefinition ,即:

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

    Then the above approach will not work.那么上面的方法就行不通了。 In fact there does not appear to be a way to generate a "normal" serialization for an instance of a type to which a converter is applied directly.事实上,似乎没有一种方法可以为直接应用转换器的类型的实例生成“正常”序列化。 Instead you would need to manually write and read each required property inside Write() and Read() , or write your own reflection code to do so automatically.相反,您需要手动写入和读取Write()Read()中的每个必需属性,或者编写自己的反射代码来自动执行此操作。

Demo fiddle here .演示小提琴在这里

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM