17

基本的に、シリアル化時の値に基づいて、生成されたJsonにプロパティを含めるか除外したいと思います。

より具体的には、値が割り当てられているかどうかを知っているタイプがあり、何かが割り当てられいる場合にのみそのタイプのプロパティをシリアル化したい(したがって、実行時に値を検査する必要があります)。APIが「デフォルト値を持っている」と「まったく指定されていない」の違いを簡単に検出できるようにしようとしています。

カスタムのJsonConverterでは不十分なようです。試してみましたが、コンバーターが呼び出される前に、プロパティ名はすでにシリアル化されていると思います。私の場合、プロパティ名も省略したいと思います。

DefaultContractResolverの拡張を見てきましたが、CreatePropertyとCreateProperties(JsonPropertyシリアル化メタデータを返す)は、シリアル化されるTypeのみを取得するため、インスタンス自体を検査できません。一般に、DefaultContractResolverには、インスタンスがシリアル化されているかどうかを制御できるものは何も表示されません。多分私はそれを逃した。

また、自分の型のカスタムJsonObjectContractを返すContractResolverを作成する必要があるかもしれないと思いました。しかし、繰り返しになりますが、インスタンスに基づいて決定を下すJsonObjectContractには何も表示されません。

私の目標を達成するための良い方法はありますか?単純なものが足りないだけですか?あなたが提供できるどんな助けも大歓迎です。Json.NETは非常に拡張性があるので、これはそれほど難しいことではないと思いました。しかし、私はここの雑草の中で私が遠く離れていると思い始めています。:)

4

2 に答える 2

9

わかりました、Json.NET ソースをしばらく掘り下げた後、ようやくこれが機能するようになり、Json.NET がサポートする ShouldSerialize* および *Specified メンバーも尊重されます。注意してください:これは間違いなく雑草になります.

そのため、DefaultContractResolver.CreateProperty によって返される JsonProperty クラスには ShouldSerialize プロパティと Converter プロパティがあり、プロパティ インスタンスを実際にシリアル化する必要があるかどうか、またシリアル化する場合はその方法を指定できることに気付きました。

ただし、逆シリアル化には少し異なるものが必要です。DefaultContractResolver.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;
    }
}

それが他の誰かに役立つことを願っています。何か不明な点がある場合や、何かを見逃しているように見える場合は、お気軽に質問してください。

于 2012-09-22T01:31:39.460 に答える
4

プロパティの値に基づいてそれを行うように求めているので、できることはデータを辞書に入れることです。ディクショナリへの値の追加を除外できます。以下は、オブジェクトからデータを取得する方法の簡単な例です。

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);
    }
}
于 2012-09-21T01:59:56.617 に答える