我想用一些元数据将一些属性包装在 JSON 对象中,无论它是否为空。但是,JsonConverter.WriteJson如果属性为 ,则不会调用我的自定义覆盖null。
当属性不为空时我得到什么:
{"Prop":{"Version":1, "Object":{"Content":"abc"}}}
Run Code Online (Sandbox Code Playgroud)
当它为空时我得到什么:
{"Prop":null}
Run Code Online (Sandbox Code Playgroud)
当它为空时我想要什么:
{"Prop":{"Version":1, "Object":null}}
Run Code Online (Sandbox Code Playgroud)
由于WriteJson从未被要求提供空值,我没有机会控制这种行为。有什么办法可以强制这样做吗?
请注意,我想知道这是否可以与转换器或合约解析器等一起使用,我不能/不想更改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;}
}
Run Code Online (Sandbox Code Playgroud)
dbc*_*dbc 10
当前无法调用 Json.NET 来JsonConverter.WriteJson()获取null值。可以看出,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
Run Code Online (Sandbox Code Playgroud)
因此,如果您需要将null成员转换为非空 JSON 值但无法修改类型本身,您有两个选择:
为父级声明手动序列化每个父级成员的类型创建自定义JsonConverter ,或者
创建一个自定义合约解析器,将成员转换为返回某些非空代理或包装对象的成员。
选项#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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后按如下方式使用它来包装 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);
Run Code Online (Sandbox Code Playgroud)
笔记:
出于此处解释的性能原因,您应该静态缓存合约解析器。
VersionWrapperProvider<T>创建一个包装对象,其中包含必要的版本信息以及Object使用 Json.NET 自己的获取和设置基础值的代理属性IValueProvider.
由于 Json.NET 不会设置预分配的引用属性的值,而是简单地使用反序列化的属性值填充它,因此 的 setter 本身有必要VersionWrapper<T>.Object在父级中设置该值。
如果您的包装类型是多态的,则您可能需要检查中CreateProperty()是否有任何基本类型。property.PropertyTypeWrappedTypes
应测试填充预先存在的Wrap使用。JsonConvert.PopulateObject
当反序列化传递给参数化构造函数的属性时,此解决方案可能不起作用。 DefaultContractResolver.CreatePropertyFromConstructorParameter在这种情况下需要修改。
工作示例 .Net fiddle在这里。
| 归档时间: |
|
| 查看次数: |
4394 次 |
| 最近记录: |