如何在不将转换器传递给反序列化对象的情况下反序列化抽象项列表

本文关键字:反序列化 对象 情况下 抽象 列表 转换器 | 更新日期: 2023-09-27 18:33:55

我有一些继承,需要一个自定义JsonConverter进行反序列化。我现在使用一种非常简单的方法,根据某些属性的存在来确定类型。

重要说明:在我的实际代码中,我无法触摸DeserializeObject调用,即我无法在那里添加自定义转换器。我知道这在某种程度上是一个XY问题,并意识到我的答案可能是我想要的是不可能的。据我所知,这使我的问题与这个问题略有不同。

这是我的情况的重现:

abstract class Mammal { }
class Cat : Mammal { public int Lives { get; set; } }
class Dog : Mammal { public bool Drools { get; set; } }
class Person
{
    [JsonConverter(typeof(PetConverter))]
    public Mammal FavoritePet { get; set; }
    [JsonConverter(typeof(PetConverter))]
    public List<Mammal> OtherPets { get; set; } 
}

这是自定义转换器:

public class PetConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) { return objectType == typeof(Mammal); }
    public override bool CanWrite { get { return false; } }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null) return null;
        JObject jsonObject = JObject.Load(reader);
        if (jsonObject["Lives"] != null) return jsonObject.ToObject<Cat>(serializer);
        if (jsonObject["Drools"] != null) return jsonObject.ToObject<Dog>(serializer);
        return null;
    }
}

这对FavoritePet工作正常,但对OtherPets来说就不那么好了,因为它是一个列表。这是一种使用 NUnit 测试重现我的问题的方法:

[TestFixture]
class MyTests
{
    [Test]
    public void CanSerializeAndDeserializeSingleItem()
    {
        var person = new Person { FavoritePet = new Cat { Lives = 9 } };
        var json = JsonConvert.SerializeObject(person);
        var actual = JsonConvert.DeserializeObject<Person>(json);
        Assert.That(actual.FavoritePet, Is.InstanceOf<Cat>());
    }
    [Test]
    public void CanSerializeAndDeserializeList()
    {
        var person = new Person { OtherPets = new List<Mammal> { new Cat { Lives = 9 } } };
        var json = JsonConvert.SerializeObject(person);
        var actual = JsonConvert.DeserializeObject<Person>(json);
        Assert.That(actual.OtherPets.Single(), Is.InstanceOf<Cat>());
    }
}

后一个测试是红色的,因为:

Newtonsoft.Json.JsonReaderException : 从 JsonReader 读取 JObject 时出错。当前 JsonReader 项不是对象:StartArray。路径"其他宠物",第 1 行,位置 33。

我也尝试过在 OtherPets 上没有自定义转换器,这导致:

Newtonsoft.Json.JsonSerializationException : 无法创建 JsonConverterLists.Mammal 类型的实例。类型是接口或抽象类,不能实例化。路径 '其他宠物[0]。生命",第 1 行,第 42 位。

明白发生了什么,我什至知道我可以用以下方法修复它:

var actual = JsonConvert.DeserializeObject<Person>(json, new PetConverter());

但是重复上面的注释:我无法更改DeserializeObject调用,因为它包装在我当前无法更改的库中的函数中。

有没有办法用基于属性的方法做同样的事情,例如,是否有一个内置的转换器用于每个条目都接受自定义转换器的列表?还是我也必须为此滚动自己的单独转换器?


脚注,如何重现:

  • Visual Studio 2013 => 全新的 .NET 4.5.1 类库
  • Install-Package Newtonsoft.Json -Version 7.0.1
  • Install-Package nunit -Version 2.6.4

您可以将上述三个代码块放在新的命名空间中并运行 NUnit 测试,看到第二个失败。

如何在不将转换器传递给反序列化对象的情况下反序列化抽象项列表

稍微

调整了转换器类。 希望很好——

public class PetConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) { return objectType == typeof(Mammal); }
    public override bool CanWrite { get { return false; } }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null) return null;
        if (reader.TokenType == JsonToken.StartArray)
        {
            var li = new List<Mammal>();
            var arr = JArray.Load(reader);
            foreach (JObject obj in arr)
            {
                if (obj["Drools"] != null)
                {
                    var k = obj.ToObject<Dog>(serializer);
                    li.Add(k);
                }
            }
            return li;
        }

        JObject jsonObject = JObject.Load(reader);
        if (jsonObject["Lives"] != null) return jsonObject.ToObject<Cat>(serializer);
        //if (jsonObject["Drools"] != null) return jsonObject.ToObject<Dog>(serializer);
        return null;
    }
}

根据@AmitKumarGhosh的建议,如果您想将列表的责任添加到同一个转换器中,您可以这样做以使测试通过:

public class PetConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) { return objectType == typeof(Mammal); }
    public override bool CanWrite { get { return false; } }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }
        if (reader.TokenType == JsonToken.StartArray)
        {
            return JArray.Load(reader)
                .Cast<JObject>()
                .Select(o => JObjectToMammal(o, serializer))
                .ToList();
        }
        return JObjectToMammal(JObject.Load(reader), serializer);
    }
    private Mammal JObjectToMammal(JObject jsonObject, JsonSerializer serializer)
    {
        if (jsonObject["Lives"] != null) return jsonObject.ToObject<Cat>(serializer);
        if (jsonObject["Drools"] != null) return jsonObject.ToObject<Dog>(serializer);
        return null;
    }
}