【问题标题】:How to serialize Task<TResult> with Json.NET?如何使用 Json.NET 序列化 Task<TResult>?
【发布时间】:2020-11-23 01:51:27
【问题描述】:

考虑

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

如何使用 json.net(反)序列化这个类?

我认为我真正想要序列化的是底层的Task.Result,然后可以使用Task.FromResult() 对其进行反序列化。如果我要使用自定义 JsonConverter,我无法通过 Attribute 传递通用 TType,以在 JsonConverter 中重建(或检索)TType 对象。因此我被卡住了。

问题来自这段代码:

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。

【问题讨论】:

  • holup...为什么我们首先要这样做Task&lt;TType&gt; toConvert你为什么需要在这里完成任务?
  • 该方法本身是有问题的,即使它适用于提供的答案。你可以read more about how to work with non-blocking code with Tasks
  • @TheGeneral 考虑在任意类型类型的通用字典中缓存 Task 的集合,根据:stackoverflow.com/questions/36084495/when-to-cache-tasks(代码在问题来自 Stephen Toub 的介绍)。我想提供一个类,它有时会为调用者提供未来,但通常带有一个已经完成的 Task,两种方式都将其暴露为 Task。如果调用者等待 Task.Result,对于缓存的任务,它将立即返回。然后我需要(反)序列化这个缓存,本质上是 ConcurrentDictionary>,但只保存 T,而不是任务本身

标签: c# json serialization json.net task


【解决方案1】:

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

【讨论】:

  • 这对我的用例毫无帮助。考虑在任意类型 Type 的通用字典中缓存 Task 的集合,按照:stackoverflow.com/questions/36084495/when-to-cache-tasks(有问题的代码来自 Stephen Toub 的介绍)。我想提供一个有时会为调用者提供未来的类,但通常会提供一个已经完成的 Task,以两种方式将其公开为 Task。如果调用者等待 Task.Result,对于缓存的任务,它将立即返回。然后我需要(反)序列化这个缓存,本质上是 ConcurrentDictionary>
  • 而且我不知道如何指示 json.net 在(反)序列化期间从 Task (取消)包装 TResult 对象,因为 TResult 可以是任何泛型类型,而 JsonConverter 接口只是递给我一个“对象”。 Main 中的代码是调用代码,我想在 InQuestion 类中实现该功能,以便它仅序列化 TResult(并从序列化未完成的任务中省略,这将非常罕见)
  • 没有办法序列化一个任务。如果你的用例要求你做不可能的事情,我会退后一步,找出你哪里出错了。
【解决方案2】:

我已经找到了解决这个问题的两种方法。

来自Add support for generic 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,它将指向JsonConverter&lt;TResult&gt; 的正确通用实例化,其中序列化很简单。这需要配置 JsonSerializerSettings 并为整个 InQuestion 类提供序列化(请注意,转换器不检查此示例中的 Task.IsCompleted)。

或者,仅在 Task&lt;T&gt; 类型的属性上使用 JsonConverterAttribute 并依靠反射从非泛型转换器中检索 TResult 类型:

    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 缺乏了解。

【讨论】:

  • 看看这个解决方案,如果任务没有完成,你会序列化一个默认的 T 实例。存在默认实例(因为它未初始化)或存在默认实例(因为这是实际正确值)之间没有区别。
猜你喜欢
  • 2022-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多