(可选)根据属性的运行时值对其进行序列化 [英] Optionally serialize a property based on its runtime value

查看:55
本文介绍了(可选)根据属性的运行时值对其进行序列化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从根本上讲,我想基于序列化时所生成的Json的值包括或忽略所生成的Json的属性.

Fundamentally, I want to include or omit a property from the generated Json based on its value at the time of serialization.

更具体地说,我有一个类型,该类型知道是否已为其分配值,并且我只想序列化该类型的属性(如果已经为它们分配了 (所以我需要在运行时检查值).我正在尝试使我的API易于检测具有默认值"和根本没有指定"之间的差异.

More-specifically, I have a type that knows if a value has been assigned to it and I only want to serialize properties of that type if there has been something assigned to it (so I need to inspect the value at runtime). I'm trying to make it easy for my API to detect the difference between "has the default value" and "wasn't specified at all".

自定义JsonConverter似乎还不够;我试了一下,我相信在调用转换器之前,属性名称已经被序列化了.就我而言,我甚至都希望省略属性名称.

A custom JsonConverter does not seem sufficient; I tried it and I believe the property name is already serialized before the converter is called. In my case I want to omit even the property name.

我已经看过扩展DefaultContractResolver,但是CreateProperty和CreateProperties(返回JsonProperty序列化元数据)仅接受要序列化的Type,因此我无法检查实例本身.通常,在DefaultContractResolver上看不到任何允许我控制 if 实例已序列化的内容.也许我错过了.

I've looked at extending DefaultContractResolver but CreateProperty and CreateProperties (which return JsonProperty serialization metadata) take only the Type being serialized, so I can't inspect the instance itself. In general, I don't see anything on the DefaultContractResolver allowing me to control if an instance is serialized; maybe I missed it.

我还认为也许我需要创建一个ContractResolver来为我的类型返回一个自定义JsonObjectContract.但是,同样,我在JsonObjectContract上看不到任何可以基于实例进行决策的东西.

I also thought maybe I needed to create a ContractResolver that returned a custom JsonObjectContract for my type. But, again, I don't see anything on JsonObjectContract that makes decisions based on an instance.

是否有实现我目标的好方法?我只是想念一些简单的东西吗?您能提供的任何帮助将不胜感激.由于Json.NET的可扩展性,我认为这不会太难.但是我开始认为我已经远离这里的杂草了. :)

Is there a good way to accomplish my goal? Am I just missing something simple? Any help you can provide is greatly appreciated. Since Json.NET is so extensible, I thought this wouldn't be too hard. But I'm starting to think I'm way off in the weeds here. :)

推荐答案

好吧,在Json.NET源代码中研究了一段时间之后,我终于完成了这项工作,它甚至将对ShouldSerialize *和* Specify成员表示敬意.NET支持.警告:这绝对是杂草.

Ok, after digging around in Json.NET source for a while, I finally got this working and it will even honor the ShouldSerialize* and *Specified members that Json.NET supports. Be warned: this is definitely going off into the weeds.

所以我意识到DefaultContractResolver.CreateProperty返回的JsonProperty类具有ShouldSerialize和Converter属性,这使我可以指定 if 该属性实例是否应该序列化,如果是,则 how 做到这一点.

So I realized that the JsonProperty class returned by DefaultContractResolver.CreateProperty has ShouldSerialize and Converter properties, which allow me to specify if the property instance should actually be serialized and, if so, how to do it.

反序列化需要一些不同.默认情况下,对于自定义类型,DefaultContractResolver.ResolveContract将返回带有null Converter属性的JsonObjectContract.为了正确反序列化我的类型,我需要在合同适用于我的类型时设置Converter属性.

Deserialization requires something a little different, though. DefaultContractResolver.ResolveContract will, by default for a custom type, return a JsonObjectContract with a null Converter property. In order to deserialize my type properly, I needed to set the Converter property when the contract is for my type.

这是代码(已删除错误处理/等以使内容尽可能小).

Here's the code (with error handling / etc removed to keep things as small as possible).

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

First, the type that needs special handling:

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 );
    }
}

在我们知道应该对其进行序列化之后,有一个转换器可以正确地对其进行序列化:

And there's the converter that will serialize it properly after we know it should be serialized:

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:

Finally, and most-verbosely, here's the ContractResolver that inserts the hooks:

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;
    }
}

我希望这对其他人有帮助.如果有任何不清楚的地方,或者好像我错过了什么,请随时提出问题.

I hope that helps somebody else. Feel free to ask questions if anything is unclear or if it looks like I missed something.

这篇关于(可选)根据属性的运行时值对其进行序列化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆