Как указать пользовательский конвертер json для общего аргумента типа свойства - PullRequest
0 голосов
/ 08 ноября 2019

Я хочу наложить несколько преобразователей JSON для одного свойства, потому что мне нужно указать конвертер для внутреннего типа свойства, который является общим.

Вариант использования:

public class A {
    public Option<DateTime> Time { get; set; }
}

У меня естьполучил OptionJsonConverter, который может десериализовать любой Option, и я хочу указать для этого свойства настраиваемую строку формата даты, используя DateFormatConverter из этого ответа .

Написание нестандартного конвертера было бы решением, но оно не идеально, поскольку у меня было бы огромное дублирование кода.

Я мог бы использовать обнуляемый тип, но я уже передал свою базу кода на Options, чтобы избежать нулевых сравнений, и эта проблема может возникнуть в будущем для других типов.

1 Ответ

0 голосов
/ 08 ноября 2019

Преобразователи могут изменять свойство Converters сериализатора при вызове ReadJson и WriteJson, а содержимое новой коллекции учитывается во время вложенных сериализаций и десериализаций.

Таким образом, мы можем создать преобразователь, который временно добавляетуказанные преобразователи в свойство Converters, например:

public abstract class CascadeJsonConverterBase : JsonConverter
{
    private readonly JsonConverter[] augmentConverters;

    protected CascadeJsonConverterBase() : this(new JsonConverter[0]) { }

    // this constructor is intended for use with JsonConverterAttribute
    protected CascadeJsonConverterBase(object[] augmentConverters)
        : this(augmentConverters.Select(FromAttributeData).ToArray())
    { }

    protected CascadeJsonConverterBase(JsonConverter[] augmentConverters)
    {
        this.augmentConverters = augmentConverters;
    }

    protected static JsonConverter FromAttributeData(object augmentConverterObj)
    {
        if (!(augmentConverterObj is object[] augmentConverter))
        {
            throw new ArgumentException($"Each augment converter data should be an object array", nameof(augmentConverters));
        }

        if (augmentConverter.Length < 1)
        {
            throw new ArgumentException($"Augment converter data should include at least one item", nameof(augmentConverters));
        }

        object augmentConverterType = augmentConverter[0];
        if (!(augmentConverterType is Type convType))
        {
            throw new ArgumentException($"Augment converter data should start with its type", nameof(augmentConverters));
        }

        if (!typeof(JsonConverter).IsAssignableFrom(convType))
        {
            throw new ArgumentException($"Augment converter type should inherit from JsonConverter abstract type", nameof(augmentConverters));
        }

        object converter = Activator.CreateInstance(convType, augmentConverter.SubArray(1, augmentConverter.Length - 1));
        return (JsonConverter)converter;
    }

    protected abstract void WriteJsonInner(JsonWriter writer, object value, JsonSerializer serializer);

    protected abstract object ReadJsonInner(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        using (AugmentedConverterScope(serializer))
        {
            WriteJsonInner(writer, value, serializer);
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        using (AugmentedConverterScope(serializer))
        {
            return ReadJsonInner(reader, objectType, existingValue, serializer);
        }
    }

    private AugmentedConverterScopeMgr AugmentedConverterScope(JsonSerializer serializer)
    {
        // add augmented converters
        for (int i = augmentConverters.Length - 1; i >= 0; i--)
        {
            serializer.Converters.Insert(0, augmentConverters[i]);
        }

        return new AugmentedConverterScopeMgr(serializer, augmentConverters.Length);
    }

    private class AugmentedConverterScopeMgr : IDisposable
    {
        private readonly JsonSerializer serializer;
        private readonly int converterCount;

        public AugmentedConverterScopeMgr(JsonSerializer serializer, int converterCount)
        {
            this.serializer = serializer;
            this.converterCount = converterCount;
        }

        public void Dispose()
        {
            // remove augmented converters
            for (int i = 0; i < converterCount; i++)
            {
                serializer.Converters.RemoveAt(0);
            }
        }
    }
}

, а затем создайте преобразователь, который оборачивает логику другого преобразователя следующим образом:

public class CascadeJsonConverter : CascadeJsonConverterBase
{
    private readonly JsonConverter wrappedConverter;

    public CascadeJsonConverter(Type wrappedConverterType, object[] wrappedConvConstructorArgs, object[] augmentConverters)
        : this(CreateConverter(wrappedConverterType, wrappedConvConstructorArgs), augmentConverters.Select(FromAttributeData).ToArray())
    { }

    public CascadeJsonConverter(JsonConverter wrappedConverter, JsonConverter[] augmentConverters)
        : base(augmentConverters)
    {
        this.wrappedConverter = wrappedConverter;
    }

    private static JsonConverter CreateConverter(Type converterType, object[] convConstructorArgs)
    {
        if (!typeof(JsonConverter).IsAssignableFrom(converterType))
        {
            throw new ArgumentException($"Converter type should inherit from JsonConverter abstract type", nameof(converterType));
        }

        return (JsonConverter) Activator.CreateInstance(converterType, convConstructorArgs);
    }

    public override bool CanConvert(Type objectType)
    {
        return wrappedConverter.CanConvert(objectType);
    }

    protected override void WriteJsonInner(JsonWriter writer, object value, JsonSerializer serializer)
    {
        wrappedConverter.WriteJson(writer, value, serializer);
    }

    protected override object ReadJsonInner(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return wrappedConverter.ReadJson(reader, objectType, existingValue, serializer);
    }
}

, который затем можно использовать для выполнениярассматриваемая цель, например, так

public class A {
    [JsonConverter(typeof(CascadeJsonConverter), // cascading converter
                   typeof(OptionJsonConverter), new object[0], // converter definition for the top-level type of the property
                   new object[] { // collection of converter definitions to use while deserializing the contents of the property
                       new object[] { typeof(DateFormatConverter), "yyyy'-'MM'-'dd'T'mm':'HH':'FF.ssK" }
                   })]
    public Option<DateTime> Time { get; set; }
}

При этом вы можете использовать не только разные контроллеры для общих полей, но и в тех случаях, когда классу необходимо изменить преобразователь для некоторого подчиненного свойства класса свойства. Neat:)

Одно предостережение для этого заключается в том, что конвертер верхнего уровня должен использовать аргумент serializer в ReadJson и WriteJson методах для чтения и записи внутренних значений вместо использования JToken.Load(reader).ToObject<T>() иJToken.FromObject(x).WriteTo(writer). В противном случае внутренние значения читаются и записываются с использованием ненастроенных сериализаторов.

Если есть более хороший способ выполнить ту же задачу, я был бы очень признателен, если бы вы поделились этим!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...