处理自定义JsonConverter的ReadJson方法中的空对象

37
我有一个Newtonsoft JSON.NET的JsonConverter,可以帮助反序列化一个抽象类类型的属性。它的主要内容如下所示:

public class PetConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Animal);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jsonObject = JObject.Load(reader);

        if (jsonObject["Lives"] != null) return jsonObject.ToObject<Cat>(serializer);
        if (jsonObject["StopPhrase"] != null) return jsonObject.ToObject<Parrot>(serializer);

        return null;
    }

    public override bool CanWrite { get { return false; } }

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

这是它处理的类:

public abstract class Animal 
{ }

public class Cat : Animal
{
    public int Lives { get; set; }
}

public class Parrot : Animal
{
    public string StopPhrase { get; set; }
}

public class Person
{
    [JsonConverter(typeof(PetConverter))]
    public Animal Pet { get; set; }
}

当反序列化一个具有非空PetPerson时,这个方法可以正常工作。但是如果Pet为空,则ReadJson方法会在第一行出现JsonReaderException错误:

'Newtonsoft.Json.JsonReaderException'类型的异常在Newtonsoft.Json.dll中发生,但在用户代码中未处理

附加信息:从JsonReader读取JObject时出错。当前JsonReader项不是对象:Null。路径'Pet',第1行,第11个位置。

我已经查看了自定义JsonConverter文档,但它仅涉及编写转换器。我尝试了以下操作:

if (reader.Value == null) return null; // this inverts the [Test] results

但是我得到了以下错误信息:

JsonSerializationException: 反序列化对象完成后,在JSON字符串中发现其他文本。

对于属性已经填充的情况。

简而言之,如何正确处理这种情况?


为了完整起见,这里提供一些单元测试,以展示手头的问题:

[TestFixture]
public class JsonConverterTests
{
    [Test]
    public void Cat_survives_serialization_roundtrip()
    {
        var person = new Person { Pet = new Cat { Lives = 9 } };
        var serialized = JsonConvert.SerializeObject(person);
        var deserialized = JsonConvert.DeserializeObject<Person>(serialized);
        Assert.That(deserialized.Pet, Is.InstanceOf<Cat>());
        Assert.That((deserialized.Pet as Cat).Lives, Is.EqualTo(9));
    }

    [Test]
    public void Parrot_survives_serialization_roundtrip()
    {
        var person = new Person { Pet = new Parrot { StopPhrase = "Lorrie!" } };
        var serialized = JsonConvert.SerializeObject(person);
        var deserialized = JsonConvert.DeserializeObject<Person>(serialized);
        Assert.That(deserialized.Pet, Is.InstanceOf<Parrot>());
        Assert.That((deserialized.Pet as Parrot).StopPhrase, Is.EqualTo("Lorrie!"));
    }

    [Test]
    public void Null_property_does_not_break_converter()
    {
        var person = new Person { Pet = null };
        var serialized = JsonConvert.SerializeObject(person);
        var deserialized = JsonConvert.DeserializeObject<Person>(serialized);
        Assert.That(deserialized.Pet, Is.Null);
    }
}
1个回答

62
在撰写问题时,特别是在撰写“我尝试了什么”部分时,我发现了一种可能的解决方案。
if (reader.TokenType == JsonToken.Null) return null;

我发布这篇文章有两个原因:

  1. 如果足够好,它可能会对有同样问题的其他人有所帮助。
  2. 我可能会从其他人的回答中了解到更好、更有竞争力的解决方案。

顺便说一句,这是一个用于非常基本的反序列化处理抽象类属性类型的完整JsonConverter:

public class PetConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Animal);
    }

    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["StopPhrase"] != null) return jsonObject.ToObject<Parrot>(serializer);

        return null;
    }

    public override bool CanWrite { get { return false; } }

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

2
或者您可以将JSON加载为“JToken”,并检查“token.Type == JTokenType.Null”。不过,实际上您的解决方案似乎是最好的。 - dbc
1
在我的情况下,当返回 null 时,reader 没有正确的状态。因此,我结合了答案的解决方案和 [dbc] 的建议:if (reader.TokenType == JsonToken.Null) { JToken.Load(reader); return null; } - JanDotNet
2
嗨Jeroen,只是写信来说你的建议帮助解决了我的问题,虽然完全诚实地说,我不知道为什么。 我最初的代码与你的类似,在ReadJson中我尝试简单地说“if(reader.Value is Animal)”,然而,无论如何,reader.Value始终为null(尽管它实际上有一个值)。 最后我只是采纳了你的想法,并像你在Lives/StopPhrase中那样使用“jsonObject[MyValue]!= null”。 - Kiran Ramaswamy
@KiranRamaswamy 很高兴听到我的问答对你有所帮助! - Jeroen
@KiranRamaswamy 我和你一样也有同样的困惑,尽管现在我明白了 reader.Valuenull,尽管它实际上有一个值,因为初始标记是一个 JsonToken.StartObject,例如字面上的 {(它本身没有一个值)! - Bilal Akil

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