Ответ 1
Хорошо, после долгого поиска в источнике Json.NET, я, наконец, получил эту работу, и она даже почитает членов, которые поддерживает JS.NET. Будьте осторожны: это определенно уходит в сорняки.
Итак, я понял, что класс JsonProperty, возвращаемый DefaultContractResolver.CreateProperty, имеет свойства ShouldSerialize и Converter, которые позволяют мне указать, действительно ли экземпляр свойства должен быть сериализован, и если да, то как это сделать.
Десериализация требует чего-то немного другого. DefaultContractResolver.ResolveContract по умолчанию для настраиваемого типа возвращает JsonObjectContract с свойством null Converter. Чтобы десериализовать мой тип должным образом, мне нужно было установить свойство 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 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 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 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;
}
}
Я надеюсь, что это поможет кому-то другому. Не стесняйтесь задавать вопросы, если что-то неясно или похоже, что я что-то пропустил.