如何强制为空值调用JsonConverter.WriteJson() [英] How to force JsonConverter.WriteJson() to be called for a null value

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

问题描述

我想用一些元数据将一些属性包装在JSON对象中,无论它是否为null.但是,如果属性为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.

当属性不为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(...) 会立即写一个空值并返回空值:

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个成员转换为非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 对于手动声明每个父级的成员的父级声明类型,或

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

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>创建一个包装器对象,该对象具有必要的版本信息以及代理Object属性,该属性使用Json.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<T>.Object的setter必须自己在父级中设置值.

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()中,您可能需要检查WrappedTypes中是否有任何property.PropertyType的基本类型.

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"rel =" nofollow noreferrer> 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小提琴此处.

Working sample .Net fiddle here.

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

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆