我想用一些元数据将一些属性包装在一个 JSON 对象中,无论它是否为 null。但是,如果属性为 null
,则不会调用我的自定义 JsonConverter.WriteJson
覆盖。
当属性不为 null 时我得到的结果:
{"Prop":{"Version":1, "Object":{"Content":"abc"}}}
当它为 null 时我得到:
{"Prop":null}
当它为 null 时我想要:
{"Prop":{"Version":1, "Object":null}}
由于从未为空值调用 WriteJson
,我没有机会控制此行为。有什么办法可以强制执行此操作吗?
请注意,我想知道这是否可能与转换器或契约(Contract)解析器有关,我不能/不想更改 MyContent
或 Wrap
类(见下文)。
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
值(value)。这可以在 JsonSerializerInternalWriter.SerializeValue(...)
中看到它立即写入一个 null 并返回一个 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 值但不能修改类型本身,您有两个选择:
创建 custom
JsonConverter
对于声明成员类型的父级,手动序列化每个父级,或者创建 custom contract resolver将成员转换为返回一些非空代理或包装对象的成员。
选项 #2 更易于维护。下面的合约解析器应该完成这项工作,包装每个成员的返回值,返回一个在传入类型列表中指定类型的值,并带有所需的版本信息:
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
的所有属性:
static IContractResolver resolver = new CustomContractResolver(new[] { typeof(MyContent) });
// And later
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(wrap, Formatting.Indented, settings);
注意事项:
出于性能原因,您应该静态缓存契约解析器 here .
VersionWrapperProvider<T>
使用必要的版本信息和代理项创建一个包装器对象Object
使用 Json.NET 自己的IValueProvider
获取和设置基础值的属性.因为 Json.NET 不会设置预分配引用属性的值,而是简单地用反序列化的属性值填充它,所以
VersionWrapper<T>.Object
的 setter 是必需的自己在父级中设置值。如果你的包装类型是多态的,在
CreateProperty()
您可能需要检查property.PropertyType
的任何基本类型在WrappedTypes
.填充预先存在的
Wrap
使用JsonConvert.PopulateObject
应该进行测试。当反序列化传递给参数化构造函数的属性时,此解决方案可能不起作用。
DefaultContractResolver.CreatePropertyFromConstructorParameter
在这种情况下需要修改。
工作示例 .Net fiddle here .
关于c# - 如何强制为空值调用 JsonConverter.WriteJson(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52518593/