如何强制为空值调用 JsonConverter.WriteJson()

时间:2023-04-25
本文介绍了如何强制为空值调用 JsonConverter.WriteJson()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

我想用一些元数据将一些属性包装在 JSON 对象中,无论它是否为空.但是,如果属性为 null,则不会调用我的自定义 JsonConverter.WriteJson 覆盖.

I want to wrap some properties in a JSON object with some metadata, regardless if it's null or not. However, my custom JsonConverter.WriteJson override is not called in case the property is null.

当属性不为空时我得到什么:

What I get when property is not null:

{"Prop":{"Version":1, "Object":{"Content":"abc"}}}

当它为空时我得到:

{"Prop":null}

当它为空时我想要:

{"Prop":{"Version":1, "Object":null}}

由于 WriteJson 从未被调用空值,我没有机会控制这种行为.有什么办法可以强制吗?

Due to WriteJson never being called for null values, I do not get the opportunity to control this behavior. Is there any way to force this?

请注意,我想知道这是否可能与转换器或合同解析器有关,我不能/不想更改 MyContentWrap类(见下文).

Note that I want to know if this is possible to do with e.g converters or contractresolvers, I can't/don't want to change the MyContent or Wrap classes (see below).

class VersioningJsonConverter : JsonConverter
{
    //Does not get called if value is null !!
    public override void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer)
    {
        writer.WriteStartObject();
        writer.WritePropertyName("v");
        writer.WriteValue(1);
        writer.WritePropertyName("o");
        if(value == null)
        {
            //never happens
            writer.WriteNull();
        }
        else
        {
            writer.WriteStartObject();
            writer.WritePropertyName("Content");
            writer.WriteValue((value as MyContent).Content);                
            writer.WriteEndObject();
        }
        writer.WriteEndObject();
    }
    public override Object ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
        => throw new NotImplementedException();
    public override Boolean CanConvert(Type objectType) => objectType == typeof(MyContent);
    public override Boolean CanRead => false;
}

public class MyContent
{
    public String Content {get;set;}
}

public class Wrap
{
    public MyContent Prop {get;set;}
}

推荐答案

目前没有办法让 Json.NET 调用 JsonConverter.WriteJson() 以获得 null价值.这可以在 JsonSerializerInternalWriter.SerializeValue(...) 立即写入 null 并返回 null 传入值:

There is no way currently to make Json.NET call JsonConverter.WriteJson() for a null value. This can be seen in JsonSerializerInternalWriter.SerializeValue(...) which immediately writes a null and returns for a null incoming value:

private void SerializeValue(JsonWriter writer, object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
{
    if (value == null)
    {
        writer.WriteNull();
        return;
    }
    // Remainder omitted

因此,如果您需要将 null 成员转换为非空 JSON 值,但不能修改类型本身,您有两种选择:

So if you need to translate null member(s) to non-null JSON value(s) but cannot modify the types themselves, you have two options:

  1. 创建一个自定义JsonConverter 父声明成员的类型手动序列化每个父,或者

  1. Create a custom JsonConverter for the parent declaring type(s) of the member(s) that serializes every parent manually, OR

创建一个自定义合约解析器将成员转换为返回一些非空代理或包装对象的成员.

Create a custom contract resolver that translates the member(s) to ones returning some non-null surrogate or wrapper object.

选项#2 更易于维护.以下合约解析器应该完成这项工作,将每个成员的返回值包装起来,返回传入类型列表中指定的类型的值以及所需的版本信息:

Option #2 is more maintainable. The following contract resolver should do the job, wrapping the returned value of every member returning a value of the type(s) specified in the incoming list of types with the required version information:

public class CustomContractResolver : DefaultContractResolver
{
    // Because contracts are cached, WrappedTypes must not be modified after construction.
    readonly HashSet<Type> WrappedTypes = new HashSet<Type>();

    public CustomContractResolver(IEnumerable<Type> wrappedTypes)
    {
        if (wrappedTypes == null)
            throw new ArgumentNullException();
        foreach (var type in wrappedTypes)
            WrappedTypes.Add(type);
    }

    class VersionWrapperProvider<T> : IValueProvider
    {
        readonly IValueProvider baseProvider;

        public VersionWrapperProvider(IValueProvider baseProvider)
        {
            if (baseProvider == null)
                throw new ArgumentNullException();
            this.baseProvider = baseProvider;
        }

        public object GetValue(object target)
        {
            return new VersionWrapper<T>(target, baseProvider);
        }

        public void SetValue(object target, object value) { }
    }

    class ReadOnlyVersionWrapperProvider<T> : IValueProvider
    {
        readonly IValueProvider baseProvider;

        public ReadOnlyVersionWrapperProvider(IValueProvider baseProvider)
        {
            if (baseProvider == null)
                throw new ArgumentNullException();
            this.baseProvider = baseProvider;
        }

        public object GetValue(object target)
        {
            return new ReadOnlyVersionWrapper<T>(target, baseProvider);
        }

        public void SetValue(object target, object value) { }
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        if (WrappedTypes.Contains(property.PropertyType) 
            && !(member.DeclaringType.IsGenericType 
                && (member.DeclaringType.GetGenericTypeDefinition() == typeof(VersionWrapper<>) || member.DeclaringType.GetGenericTypeDefinition() == typeof(ReadOnlyVersionWrapper<>))))
        {
            var wrapperGenericType = (property.Writable ? typeof(VersionWrapper<>) : typeof(ReadOnlyVersionWrapper<>));
            var providerGenericType = (property.Writable ? typeof(VersionWrapperProvider<>) : typeof(ReadOnlyVersionWrapperProvider<>));
            var wrapperType = wrapperGenericType.MakeGenericType(new[] { property.PropertyType });
            var providerType = providerGenericType.MakeGenericType(new[] { property.PropertyType });
            property.PropertyType = wrapperType;
            property.ValueProvider = (IValueProvider)Activator.CreateInstance(providerType, property.ValueProvider);
            property.ObjectCreationHandling = ObjectCreationHandling.Reuse;
        }

        return property;
    }
}

internal class VersionWrapper<T>
{
    readonly object target;
    readonly IValueProvider baseProvider;

    public VersionWrapper(object target, IValueProvider baseProvider)
    {
        this.target = target;
        this.baseProvider = baseProvider;
    }

    public int Version { get { return 1; } }

    [JsonProperty(NullValueHandling = NullValueHandling.Include)]
    public T Object 
    {
        get
        {
            return (T)baseProvider.GetValue(target);
        }
        set
        {
            baseProvider.SetValue(target, value);
        }
    }
}

internal class ReadOnlyVersionWrapper<T>
{
    readonly object target;
    readonly IValueProvider baseProvider;

    public ReadOnlyVersionWrapper(object target, IValueProvider baseProvider)
    {
        this.target = target;
        this.baseProvider = baseProvider;
    }

    public int Version { get { return 1; } }

    [JsonProperty(NullValueHandling = NullValueHandling.Include)]
    public T Object
    {
        get
        {
            return (T)baseProvider.GetValue(target);
        }
    }
}

然后如下使用它来包装MyContent类型的所有属性:

Then use it as follows to wrap all properties of type MyContent:

static IContractResolver resolver = new CustomContractResolver(new[] { typeof(MyContent) });

// And later
var settings = new JsonSerializerSettings
{
    ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(wrap, Formatting.Indented, settings);

注意事项:

  • 出于性能原因,您应该静态缓存合约解析器,此处.

VersionWrapperProvider<T> 创建一个包含必要版本信息的包装器对象以及一个使用 Json 获取和设置基础值的代理 Object 属性.NET 自己的 IValueProvider.

VersionWrapperProvider<T> creates a wrapper object with the necessary version information as well as a surrogate Object property that gets and sets the underlying value using Json.NET's own IValueProvider.

因为 Json.NET 不设置预分配的引用属性的值,而是简单地用反序列化的属性值填充它,所以 VersionWrapper.Object 为自己设置父级中的值.

Because Json.NET does not set back the value of a pre-allocated reference property, but instead simply populates it with the deserialized property values, it is necessary for the setter of VersionWrapper<T>.Object to itself set the value in the parent.

如果您的包装类型是多态的,则在 CreateProperty() 中您可能需要检查 property.PropertyType 的任何基本类型是否在 WrappedTypes.

If your wrapped types are polymorphic, in CreateProperty() you may need to check whether any of the base types of property.PropertyType are in WrappedTypes.

使用 Wrap">JsonConvert.PopulateObject 应该被测试.

Populating a pre-existing Wrap using JsonConvert.PopulateObject should be tested.

在反序列化传递给参数化构造函数的属性时,此解决方案可能不起作用.DefaultContractResolver.CreatePropertyFromConstructorParameter 需要修改这样的情况.

This solution may not work when deserializing properties passed to parameterized constructors. DefaultContractResolver.CreatePropertyFromConstructorParameter would need modification in such a situation.

工作示例 .Net fiddle 这里.

Working sample .Net fiddle here.

这篇关于如何强制为空值调用 JsonConverter.WriteJson()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:在返回给客户端之前修改 JSON 的通用方法 下一篇:覆盖 Json 反序列化带有前导零的数字作为十进制而不是八进制值

相关文章