Как сериализовать Task <TResult>с Json. NET? - PullRequest
0 голосов
/ 03 августа 2020

Рассмотрим

public class InQuestion<TType>
{
    // JsonConverter(typeof CustomConverter))
    public Task<TType> toConvert { get; set; }
}

Как я могу (де) сериализовать этот класс с помощью json. net?

То, что я думаю, я действительно хочу serialize - это базовая задача Task .Result, которую затем можно десериализовать с помощью Task .FromResult (). Если я использую специальный JsonConverter, я не могу передать generi c TType через Attribute, чтобы восстановить (или получить) объект TType в JsonConverter. Следовательно, я застрял.

Вопрос возник из этого кода:

public class Program
{
    public class InQuestion<TType>
    {
        public Task<TType> toConvert { get; set; }
    }

    public class Result
    {
        public int value { get; set; }
    }

    public static async Task Main()
    {
        var questionable = new InQuestion<Result>();
        questionable.toConvert = Task.Run(async () => new Result { value = 42 });
        await questionable.toConvert; 

        string json = JsonConvert.SerializeObject(questionable);
        Debug.WriteLine(json); 
        InQuestion<Result> back = JsonConvert.DeserializeObject(json, typeof(InQuestion<Result>)) as InQuestion<Result>;
        Debug.Assert(back?.toConvert?.Result?.value == 42); 
    }
}

который, как ни удивительно для меня, останавливает во время вызова JsonConvert.DeserializeObject . https://github.com/JamesNK/Newtonsoft.Json/issues/1886 говорит о проблеме и рекомендует разумно «Никогда не сериализовать / десериализовать задачу», но на самом деле не дает советов, как сериализовать базовую задачу .Result.

Ответы [ 2 ]

1 голос
/ 03 августа 2020

A Task - это обещание будущего значения, и, конечно, вы не можете сериализовать значение, которое еще не было предоставлено.

Поскольку объект InQuestion содержит член Task, вы не может сериализовать и десериализовать объект InQuestion.

Обходной путь - сериализовать результат и восстановить объект InQuestion после десериализации.

public static async Task Main()
{
    var questionable = new InQuestion<Result>();
    questionable.toConvert = Task.Run(async () => new Result { value = 42 });

    Result result = await questionable.toConvert;
    string json = JsonConvert.SerializeObject(result);
    
    Result back = JsonConvert.DeserializeObject(json, typeof<Result>) as Result;

    InQuestion<Result> reconstructed = new InQuestion<Result>()
    {
        toConvert = Task.FromResult(back)
    };
}
0 голосов
/ 03 августа 2020

Я нашел два решения этой проблемы.

Из Добавить поддержку generi c JsonConverter instantiation :

    [JsonConverter(typeof(InQuestionConverter<>))]
    public class InQuestion<TResult>
    {
        public Task<TResult> toConvert { get; set; }
    }

    public class Result
    {
        public int value { get; set; }
        public string text { get; set; }

        public override bool Equals(object obj)
        {
            return obj is Result result &&
                   value == result.value &&
                   text == result.text;
        }
    }

    public class InQuestionConverter<TResult> : JsonConverter<InQuestion<TResult>>
    {
        public override InQuestion<TResult> ReadJson(JsonReader reader, Type objectType, InQuestion<TResult> existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            if (hasExistingValue)
                existingValue.toConvert = Task.FromResult(serializer.Deserialize<TResult>(reader));
            else
                existingValue = new InQuestion<TResult>
                {
                    toConvert = Task.FromResult(serializer.Deserialize<TResult>(reader))
                };
            return existingValue;
        }

        public override void WriteJson(JsonWriter writer, InQuestion<TResult> value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value.toConvert.Result, typeof(TResult));
        }
    }

    public sealed class CustomContractResolver : DefaultContractResolver
    {
        protected override JsonConverter ResolveContractConverter(Type objectType)
        {
            var typeInfo = objectType.GetTypeInfo();
            if (typeInfo.IsGenericType && !typeInfo.IsGenericTypeDefinition)
            {
                var jsonConverterAttribute = typeInfo.GetCustomAttribute<JsonConverterAttribute>();
                if (jsonConverterAttribute != null && jsonConverterAttribute.ConverterType.GetTypeInfo().IsGenericTypeDefinition)
                {
                    Type t = jsonConverterAttribute.ConverterType.MakeGenericType(typeInfo.GenericTypeArguments);
                    object[] parameters = jsonConverterAttribute.ConverterParameters;
                    return (JsonConverter)Activator.CreateInstance(t, parameters);
                }
            }
            return base.ResolveContractConverter(objectType);
        }
    }

    public static void Main()
    {
        var questionable = new InQuestion<Result>();
        questionable.toConvert = Task.Run(async () => { return new Result { value = 42, text = "fox" };  });
        questionable.toConvert.Wait();

        string json = JsonConvert.SerializeObject(questionable, Formatting.None, new JsonSerializerSettings { ContractResolver = new CustomContractResolver() });
        InQuestion<Result> back = JsonConvert.DeserializeObject(json, typeof(InQuestion<Result>), new JsonSerializerSettings { ContractResolver = new CustomContractResolver() }) as InQuestion<Result>;

        Debug.Assert(back.toConvert.Result.Equals(questionable.toConvert.Result));
        return;
    }

Включает пользовательский ContractResolver который будет указывать на правильную генерацию c экземпляра JsonConverter<TResult>, в котором сериализация является простой. Для этого требуется настроить JsonSerializerSettings и обеспечить сериализацию для всего класса InQuestion (обратите внимание, что конвертер не проверяет Task.IsCompleted в этом примере).

В качестве альтернативы, используя JsonConverterAttribute только для свойств типа Task<T> и полагаясь на отражение, чтобы получить тип TResult из не-generi c Converter:

    public class InQuestion<TResult>
    {
        [JsonConverter(typeof(FromTaskOfTConverter))]
        public Task<TResult> toConvert { get; set; }
    }

    public class FromTaskOfTConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return IsDerivedFromTaskOfT(objectType);
        }

        static bool IsDerivedFromTaskOfT(Type type)
        {
            while (type.BaseType != typeof(object))
            {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>))
                    return true;
                type = type.BaseType;
            }
            return false;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            Debug.Assert(IsDerivedFromTaskOfT(objectType));
            Type TResult = objectType.GetGenericArguments()[0];
            object ResultValue = serializer.Deserialize(reader, TResult);
            return typeof(Task).GetMethod("FromResult").MakeGenericMethod(TResult).Invoke(null, new[] { ResultValue });
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            Type objectType = value.GetType();
            Debug.Assert(IsDerivedFromTaskOfT(objectType));

            Type TResult = objectType.GetGenericArguments()[0];
            Type TaskOfTResult = typeof(Task<>).MakeGenericType(TResult);

            if ((bool)TaskOfTResult.GetProperty("IsCompleted").GetValue(value) == true)
            {
                object ResultValue = TaskOfTResult.GetProperty("Result").GetValue(value);
                serializer.Serialize(writer, ResultValue, TResult);
            }
            else
            {
                serializer.Serialize(writer, Activator.CreateInstance(TResult));
            }
        }
    }

    public static void Main()
    {
        var questionable = new InQuestion<Result>();
        questionable.toConvert = Task.Run(async () => { return new Result { value = 42, text = "fox" }; });
        questionable.toConvert.Wait();

        string json = JsonConvert.SerializeObject(questionable);
        InQuestion<Result> back = JsonConvert.DeserializeObject(json, typeof(InQuestion<Result>)) as InQuestion<Result>;

        Debug.Assert(back.toConvert.Result.Equals(questionable.toConvert.Result));
        return;
    }

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

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