从根本上讲,我希望根据序列化时生成的Json的值,从中包含或省略一个属性。
更具体地说,我有一个类型,它知道是否已经为它分配了值,并且我只想在已经为其分配了的情况下序列化该类型的属性(所以我需要在运行时检查该值)。我试图让我的API更容易地检测"具有默认值"one_answers"根本没有指定"之间的差异。
自定义JsonConverter似乎还不够;我试过了,我相信在调用转换器之前,属性名称已经序列化了。在我的情况下,我甚至想省略财产名称。
我已经考虑过扩展DefaultContractResolver,但CreateProperty和CreateProperties(返回JsonProperty序列化元数据)只接受正在序列化的Type,所以我无法检查实例本身。通常,我在DefaultContractResolver上看不到任何东西允许我在实例被序列化时控制;也许我错过了。
我还想,也许我需要创建一个ContractResolver,它为我的类型返回一个自定义JsonObjectContract。但是,我在JsonObjectContract上看不到任何基于实例进行决策的内容。
有什么好方法可以实现我的目标吗?我只是错过了一些简单的东西吗?非常感谢您能提供的任何帮助。自从Json。NET是如此的可扩展,我想这不会太难。但我开始觉得我离这里的杂草太远了
好的,在Json周围挖掘之后。NET源代码一段时间后,我终于完成了这项工作,它甚至会向Json的ShouldSerialize*和*Specified成员致敬。NET支持。请注意:这肯定会变成杂草。
因此,我意识到DefaultContractResolver返回的JsonProperty类。CreateProperty具有ShouldSerialize和Converter属性,这允许我指定属性实例是否应该被序列化,如果应该,如何进行序列化。
不过,反序列化需要一些不同的东西。默认ContractResolver。默认情况下,对于自定义类型,ResolveContract将返回具有null Converter属性的JsonObjectContract。为了正确地反序列化我的类型,我需要在约定适用于我的类型时设置Converter属性。
以下是代码(删除了错误处理等以使事情尽可能小)。
首先,需要特殊处理的类型:
public struct Optional<T>
{
public readonly bool ValueProvided;
public readonly T Value;
private Optional( T value )
{
this.ValueProvided = true;
this.Value = value;
}
public static implicit operator Optional<T>( T value )
{
return new Optional<T>( value );
}
}
在我们知道它应该被序列化后,有一个转换器会正确地将其序列化:
public class OptionalJsonConverter<T> : JsonConverter
{
public static OptionalJsonConverter<T> Instance = new OptionalJsonConverter<T>();
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
var optional = (Optional<T>)value; // Cast so we can access the Optional<T> members
serializer.Serialize( writer, optional.Value );
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
var valueType = objectType.GetGenericArguments()[ 0 ];
var innerValue = (T)serializer.Deserialize( reader, valueType );
return (Optional<T>)innerValue; // Explicitly invoke the conversion from T to Optional<T>
}
public override bool CanConvert( Type objectType )
{
return objectType == typeof( Optional<T> );
}
}
最后,也是最详细的,这里是插入钩子的ContractResolver:
public class CustomContractResolver : DefaultContractResolver
{
// For deserialization. Detect when the type is being deserialized and set the converter for it.
public override JsonContract ResolveContract( Type type )
{
var contract = base.ResolveContract( type );
if( contract.Converter == null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof( Optional<> ) )
{
// This may look fancy but it's just calling GetOptionalJsonConverter<T> with the correct T
var optionalValueType = type.GetGenericArguments()[ 0 ];
var genericMethod = this.GetAndMakeGenericMethod( "GetOptionalJsonConverter", optionalValueType );
var converter = (JsonConverter)genericMethod.Invoke( null, null );
// Set the converter for the type
contract.Converter = converter;
}
return contract;
}
public static OptionalJsonConverter<T> GetOptionalJsonConverter<T>()
{
return OptionalJsonConverter<T>.Instance;
}
// For serialization. Detect when we're creating a JsonProperty for an Optional<T> member and modify it accordingly.
protected override JsonProperty CreateProperty( MemberInfo member, MemberSerialization memberSerialization )
{
var jsonProperty = base.CreateProperty( member, memberSerialization );
var type = jsonProperty.PropertyType;
if( type.IsGenericType && type.GetGenericTypeDefinition() == typeof( Optional<> ) )
{
// This may look fancy but it's just calling SetJsonPropertyValuesForOptionalMember<T> with the correct T
var optionalValueType = type.GetGenericArguments()[ 0 ];
var genericMethod = this.GetAndMakeGenericMethod( "SetJsonPropertyValuesForOptionalMember", optionalValueType );
genericMethod.Invoke( null, new object[]{ member.Name, jsonProperty } );
}
return jsonProperty;
}
public static void SetJsonPropertyValuesForOptionalMember<T>( string memberName, JsonProperty jsonProperty )
{
if( jsonProperty.ShouldSerialize == null ) // Honor ShouldSerialize*
{
jsonProperty.ShouldSerialize =
( declaringObject ) =>
{
if( jsonProperty.GetIsSpecified != null && jsonProperty.GetIsSpecified( declaringObject ) ) // Honor *Specified
{
return true;
}
object optionalValue;
if( !TryGetPropertyValue( declaringObject, memberName, out optionalValue ) &&
!TryGetFieldValue( declaringObject, memberName, out optionalValue ) )
{
throw new InvalidOperationException( "Better error message here" );
}
return ( (Optional<T>)optionalValue ).ValueProvided;
};
}
if( jsonProperty.Converter == null )
{
jsonProperty.Converter = CustomContractResolver.GetOptionalJsonConverter<T>();
}
}
// Utility methods used in this class
private MethodInfo GetAndMakeGenericMethod( string methodName, params Type[] typeArguments )
{
var method = this.GetType().GetMethod( methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static );
return method.MakeGenericMethod( typeArguments );
}
private static bool TryGetPropertyValue( object declaringObject, string propertyName, out object value )
{
var propertyInfo = declaringObject.GetType().GetProperty( propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance );
if( propertyInfo == null )
{
value = null;
return false;
}
value = propertyInfo.GetValue( declaringObject, BindingFlags.GetProperty, null, null, null );
return true;
}
private static bool TryGetFieldValue( object declaringObject, string fieldName, out object value )
{
var fieldInfo = declaringObject.GetType().GetField( fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance );
if( fieldInfo == null )
{
value = null;
return false;
}
value = fieldInfo.GetValue( declaringObject );
return true;
}
}
我希望这能帮助其他人。如果有什么不清楚的地方,或者看起来我遗漏了什么,请随时提问。
因为您要求根据属性的值来执行,所以您可以将数据放入字典中。您可以排除将值添加到字典中。下面是一个如何从对象中获取数据的简单示例。
public class Class1
{
public string Name { get; set; }
}
[TestFixture]
public class Tests
{
[Test]
public void ConvertTest()
{
var dictionary = new Dictionary<string, object>();
var @class = new Class1 { Name = "Joe" };
var propertyInfos = typeof (Class1).GetProperties();
foreach (PropertyInfo propertyInfo in propertyInfos)
{
dictionary.Add(propertyInfo.Name, propertyInfo.GetValue(@class, BindingFlags.GetProperty, null, null, null));
}
var serializeObject = JsonConvert.SerializeObject(dictionary);
var o = JsonConvert.SerializeObject(@class);
Console.WriteLine(serializeObject);
Console.WriteLine(o);
var class1 = JsonConvert.DeserializeObject<Class1>(serializeObject);
Console.WriteLine(class1.Name);
}
}