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

时间:2018-09-26 13:10:55

标签: c# json.net

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

当属性不为null时,我会得到什么:

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

得到的为空:

{"Prop":null}

想要为空时的内容:

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

由于从未为空值调用WriteJson,因此我没有机会控制这种行为。有什么方法可以强制执行此操作吗?

请注意,我想知道这是否可以与转换器或合同解析器一起使用,我不能/不想更改MyContentWrap类(请参见下文)。

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

1 个答案:

答案 0 :(得分:2)

当前尚无办法让Json.NET调用JsonConverter.WriteJson()值。可以在JsonSerializerInternalWriter.SerializeValue(...)中看到,它立即写入一个空值并返回空值:

null

因此,如果您需要将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值,但无法自行修改类型,则有两个选择:

  1. 为父级声明成员的类型创建custom JsonConverter ,以手动序列化每个父级,或者

  2. 创建一个custom contract resolver,将成员转换为返回一些非null代理或包装对象的成员。

选项2更易于维护。以下合同解析器应完成此工作,包装返回返回类型列表中指定类型的值的每个成员的返回值以及所需的版本信息:

null

然后按如下所示使用它包装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

注意:

  • 出于性能原因,here中应静态缓存合同解析器。

  • 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.NET自己的Object获取并设置基础值。 / p>

    因为Json.NET不会回退预分配的引用属性的值,而是仅使用反序列化的属性值填充它,所以IValueProvider的setter必须自己在父母。

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

  • 使用JsonConvert.PopulateObject填充先前存在的WrappedTypes

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

工作示例.Net小提琴here