如何使用Json.Net将泛型接口反序列化为具体类型?

4

我有以下接口:

public interface IInterface<out M>
{
    M Message { get; }
    string Str { get; }
}

以及它的实现:

public class Implementation<M> : IInterface<M>
{
    public M Message;
    public string Str;

    public Implementation(M message, string str)
    {
        Message = message;
        Str = str;
    }

    M IInterface<M>.Message => this.Message;
    string IInterface<M>.Str => this.Str;
}

这里是一个M系列的样品:

public class Sample
{
    public int X;
}

以下是我从 JavaScript 客户端传递的示例 JSON:

{ "Message" : { "X": 100 }, "Str" : "abc" }

现在有一些遗留/外部代码(我无法更改),试图使用Json.Net使用DeserializeObject<IInterface<Sample>>(js_object_string)对上述JSON对象进行反序列化。

如何为这个IInterface接口编写一个JsonConverter,以处理其泛型参数M。大多数互联网上的解决方案只适用于编译时已知的类型。
我尝试了下面的代码(我没有完全理解),但外部代码认为反序列化对象不是IInterface
static class ReflectionHelper
{
    public static IInterface<T> Get<T>()
    {
        var x = JsonConvert.DeserializeObject<T>(str);
        IInterface<T> y = new Implementation<T>(x, "xyz");
        return y;
    }
}

class MyConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
       return (objectType == typeof(IInterface<>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
       var w = Newtonsoft.Json.Linq.JObject.Load(reader);
       var x = typeof(ReflectionHelper).GetMethod(nameof(ReflectionHelper.Get)).MakeGenericMethod(objectType.GetGenericArguments()[0]).Invoke(null, new object[] {  });

       return x;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
       serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // otherwise I get a circular dependency error.
       serializer.Serialize(writer, value);
    }
}

1
你是否总是希望将IInterface<M>反序列化为Implementation<M> - dbc
是的。因为 IInterface 只有一个名为 Implementation 的实现,我确信它会一直保持这样。至于为什么我需要一个接口的问题,那是一个很长很长的故事,超出了本问题的范围。 - AbbasFaisal
好的。如果您无法修改对“DeserializeObject<IInterface<Sample>>(js_object_string)”的调用并直接传递它,则我更新了我的答案,提供了几种不同的应用“MyConverter”的方法。 - dbc
1个回答

4

您的MyConverter可以编写如下:

public class MyConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) =>
       objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(IInterface<>);

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (!CanConvert(objectType)) // For safety.
            throw new ArgumentException(string.Format("Invalid type {0}", objectType));
        var concreteType = typeof(Implementation<>).MakeGenericType(objectType.GetGenericArguments());
        return serializer.Deserialize(reader, concreteType);
    }

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

然后将其添加到Converters中,用于序列化和反序列化,如下所示:

var settings = new JsonSerializerSettings
{
    Converters = { new MyConverter() },
};
var root = JsonConvert.DeserializeObject<IInterface<Sample>>(js_object_string, settings);

如果您确实不能完全更改调用DeserializeObject<IInterface<Sample>>(js_object_string),您可以像以下方式一样将转换器添加到Json.NET的当前线程的全局默认设置中:

// Set up Json.NET's global default settings to include MyConverter
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
    {
        Converters = { new MyConverter() },
    };

// And then later, deserialize to IInterface<Sample> via a call that cannot be changed AT ALL:
var root = JsonConvert.DeserializeObject<IInterface<Sample>>(js_object_string);

或者,您可以直接将 MyConverter 应用于 IInterface<out M>,如下所示:

[JsonConverter(typeof(MyConverter))]
public interface IInterface<out M>
{

但是如果你这样做,你必须将此回答中的NoConverter应用于如何使用Json.Net将通用接口反序列化为通用实现类型?中的Implementation<M>,以避免出现堆栈溢出异常:

[JsonConverter(typeof(NoConverter))]
public class Implementation<M> : IInterface<M>
{

注意事项:

  • 通过重写JsonConverter.CanWrite并返回false,我们避免了实现WriteJson()的需要。

  • ReadJson()中,我们通过从传入的objectType中提取泛型参数来确定要反序列化的具体类型,该类型必须为IInterface<M>,其中一些M,并且使用相同的泛型参数构造一个具体类型Implementation<M>

  • JSON.NET支持从带参数的构造函数进行反序列化,如JSON.net: how to deserialize without using the default constructor?所述。由于您的Implementation<M>具有符合要求的单个带参数的构造函数,因此它被调用以正确反序列化您的具体类。

  • DefaultSettings适用于应用程序中所有对JsonConvert的调用,来自所有线程,因此您应确定修改这些设置是否适合您的应用程序。

  • 必须将NoConverter应用于Implementation<M>,因为在没有自己的转换器的情况下,它将从IInterface<out M>继承MyConverter,随后在反序列化具体类型时会导致对MyConverter.ReadJson()的递归调用,从而导致堆栈溢出或循环引用异常。(您可以自行调试以确认。)

    有关生成“默认”反序列化具体类的其他选项,而不使用转换器,请参见JSON.Net throws StackOverflowException when using [JsonConvert()]Call default JsonSerializer in a JsonConverter for certain value type arrays。 建议构造具体类型的默认实例,然后使用JsonSerializer.Populate()填充它的答案对您不起作用,因为您的具体类型没有默认构造函数。

这里提供了关于DefaultSettings的演示示例,以及MyConverterNoConverter的演示示例这里


1
感谢您添加如此详细的答案。我已经检查过了,第一次使用似乎就可以工作。一旦我完全了解了其他使用此代码的事情,我将标记它为正确的答案。然而还有几个问题:1. 为什么我会因为循环依赖而不得不应用NoConverter?2. 我们不能有比NoConverter更好的东西吗?因为这看起来并不优雅。3. WriteJson是什么,它有什么用途? - AbbasFaisal
1
@AbbasFaisal - 1) 因为 Implementation<M> 将继承 IInterface<M> 的转换器,除非它有自己的转换器来替代它,这会导致递归调用 MyConverter.ReadJson()。您可以自己调试以验证。2) 这主要是一个观点问题。另一种常见的解决方案是通过默认构造函数构造具体对象并进行填充,但是您的 Implementation<M> 需要一个带参数的构造函数。 - dbc
1
文档中得知:编写对象的JSON表示形式。 如果您只想编写默认的JSON表示形式,则从CanWrite返回false会导致Json.NET跳过调用WriteJson() - dbc
关于添加NoConverter的问题,我还有一个问题。如果我们不添加NoConverter,那么当调用Implementation的ReadJson时,if (CanConvert)会抛出ArgumentException,这与NoConverter在ReadJson内部执行的操作相同。那么为什么我们仍然需要NoConverter呢?我实际上尝试过没有NoConverter,但是它不起作用,我不明白为什么。 - AbbasFaisal
1
@AbbasFaisal - 在ReadJson()内调用CanConvert(objectType)确实会导致参数异常而不是堆栈溢出异常,参见https://dotnetfiddle.net/NE2v5K。 但是由于异常而导致反序列化终止,因此它不起作用。 此外,当通过属性应用转换器时(例如[JsonConverter(typeof(MyConverter))]),则Json.NET假定它能够转换类型(及其继承类型),因此不会调用CanConvert() - dbc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接