如何强制为空值调用JsonConverter.WriteJson() [英] How to force JsonConverter.WriteJson() to be called for a null value
问题描述
我想用一些元数据将一些属性包装在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?
请注意,我想知道这是否可以与转换器或合同解析器一起使用,我不能/不想更改MyContent
或Wrap
类(见下文).
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:
-
创建自定义
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
.
使用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屋!