C# (可选)根据属性的运行时值序列化属性

C# (可选)根据属性的运行时值序列化属性,c#,.net,json,serialization,json.net,C#,.net,Json,Serialization,Json.net,基本上,我希望根据序列化时生成的Json的值,在其中包含或省略一个属性 更具体地说,我有一个类型,它知道是否已经为它分配了值,并且我只想序列化该类型的属性,如果已经为它分配了某些内容(因此我需要在运行时检查该值)。我试图让我的API更容易检测“具有默认值”和“根本没有指定”之间的差异 自定义JsonConverter似乎不够;我试过了,我相信在调用转换器之前属性名已经序列化了。在我的例子中,我甚至想省略属性名 我已经研究了扩展DefaultContractResolver,但是CreatePro

基本上,我希望根据序列化时生成的Json的值,在其中包含或省略一个属性

更具体地说,我有一个类型,它知道是否已经为它分配了值,并且我只想序列化该类型的属性,如果已经为它分配了某些内容(因此我需要在运行时检查该值)。我试图让我的API更容易检测“具有默认值”和“根本没有指定”之间的差异

自定义JsonConverter似乎不够;我试过了,我相信在调用转换器之前属性名已经序列化了。在我的例子中,我甚至想省略属性名

我已经研究了扩展DefaultContractResolver,但是CreateProperty和CreateProperties(返回JsonProperty序列化元数据)只接受被序列化的类型,因此我无法检查实例本身。通常,我在DefaultContractResolver上看不到允许我控制实例是否序列化的任何内容;也许我错过了

我还认为可能需要创建一个ContractResolver,为我的类型返回一个定制的JsonObjectContract。但是,在JsonObjectContract中,我没有看到任何基于实例的决策


有什么好方法可以实现我的目标吗?我只是错过了一些简单的东西吗?非常感谢您提供的任何帮助。由于Json.NET是如此可扩展,我认为这不会太难。但我开始觉得我已经远离了这里的杂草

因为您要求根据属性的值执行此操作,所以您可以将数据放入字典中。您可以排除将值添加到字典中。下面是如何从对象中获取数据的简单示例

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);
    }
}
公共类1
{
公共字符串名称{get;set;}
}
[测试夹具]
公开课考试
{
[测试]
公共测试()
{
var dictionary=newdictionary();
var@class=newclass1{Name=“Joe”};
var propertyInfos=typeof(Class1).GetProperties();
foreach(PropertyInfo PropertyInfo in propertyInfos中的PropertyInfo)
{
Add(propertyInfo.Name,propertyInfo.GetValue(@class,BindingFlags.GetProperty,null,null));
}
var serializeObject=JsonConvert.serializeObject(字典);
var o=JsonConvert.SerializeObject(@class);
Console.WriteLine(序列化对象);
控制台写入线(o);
var class1=JsonConvert.DeserializeObject(serializeObject);
Console.WriteLine(class1.Name);
}
}

好的,在对Json.NET源代码进行了一段时间的深入研究之后,我终于实现了这一点,它甚至将尊重Json.NET支持的ShouldSerialize*和*指定成员。请注意:这肯定会让你陷入困境

因此,我意识到DefaultContractResolver.CreateProperty返回的JsonProperty类具有ShouldSerialize和Converter属性,这允许我指定属性实例是否应该实际序列化,如果应该,如何序列化

然而,反序列化需要一些不同的东西。默认情况下,对于自定义类型,ResolveContractResolver.ResolveContract将返回具有null转换器属性的JsonObjectContract。为了正确地反序列化我的类型,我需要在合同是我的类型时设置Converter属性

下面是代码(删除了错误处理/etc,以尽可能地减少事情)

首先,需要特殊处理的类型:

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结构可选
{
提供公共只读布尔值;
公共只读T值;
私有可选(T值)
{
this.ValueProvided=true;
这个。值=值;
}
公共静态隐式运算符可选(T值)
{
返回新的可选值;
}
}
还有一个转换器,在我们知道它应该被序列化之后,它将被正确地序列化:

公共类选项JsonConverter:JsonConverter
{
公共静态OptionalJsonConverter实例=新建OptionalJsonConverter();
公共重写void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器)
{
var optional=(可选)value;//强制转换,以便我们可以访问可选成员
serializer.Serialize(writer,可选.Value);
}
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
var valueType=objectType.GetGenericArguments()[0];
var innerValue=(T)序列化程序。反序列化(读取器,valueType);
return(可选)innerValue;//显式调用从T到Optional的转换
}
公共覆盖布尔CanConvert(类型objectType)
{
return objectType==typeof(可选);
}
}
最后,最详细地说,这里是插入钩子的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;
    }
}
公共类CustomContractResolver:DefaultContractResolver
{
//用于反序列化。检测类型何时被反序列化,并为其设置转换器。
公共重写JsonContract ResolveContract(类型)
{
var合同=基本合同(类型);
if(contract.Converter==null&&type.IsGenericType&&type.GetGenericTypeDefinition()==typeof(可选))
{
//这可能看起来很花哨,但它只是用正确的T调用GetOptionalJsonConverter
var optionalValueType=type.GetGenericArguments()[0];
var genericMethod=this.GetAndMakeGenericMethod(“GetOptionalJsonConverter”,optionalValueType);
var converter=(JsonConverter)genericMethod.Invoke(null,null);
//设置类型的转换器
合同.转换器=转换器;
}
退货合同;
}
公共静态选项JSONConverter GetOptionalJsonConvert
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;
    }
}