5

我有一个我只想在反序列化时使用的转换器。所以我将 CanWrite 设置为 false,它工作正常并且一切都可以序列化。然后,Json 字符串包含一个对象图,其中有一个 SantaClauseCollection,其中包含一个 SantaClause 项目数组和一个 $type,表明它们是具体类型 SantaClause。

但是,当它在反序列化时遇到 SantaClaus 的集合时,它从不调用 CanConvert(我有一个断点并看到 SantaClausCollection,按 F5 继续,当遇到 SantaClaus 集合中的项目时,它应该再次点击断点,但它没有)。当它到达 SantaClaus 项目时,它不会尝试调用 CanConvert。甚至没有为该项目调用 CanConvert 来检查我的转换器是否会处理它,而是尝试自行反序列化它,这不起作用,因为该类没有默认构造函数,也没有具有属性名称匹配约定的构造函数:

找不到用于 SantaClaus 类型的构造函数。一个类应该有一个默认构造函数、一个带参数的构造函数或一个标有 JsonConstructor 属性的构造函数。

我理解为什么会出现这个错误,但问题是它表明 Json.net 试图反序列化对象,而不是调用 CanConvert 来检查我的转换器是否想要处理反序列化。

为什么没有为集合中的每个项目调用 CanConvert?

我的转换器:

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

    /// <summary>
    /// Deserializes a SantaClaus as a SantaClausEx which has a matching constructor that allows it to deserialize naturally.
    /// </summary>       
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize<SantaClausEx>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }


    public override bool CanRead
    {
        get
        {
            return true;
        }
    }

    public override bool CanWrite
    {
        get
        {
            return false;//We only need this converter when reading.
        }
    }

}

SantaClausEx 只是继承自 SantaClaus 添加一个带有重命名参数的构造函数来匹配属性:

class SantaClaus //a third party class I can't modify
{
    string Name {get;set;}
    public SantaClaus(string santaClauseName) { this.Name = santaClauseName }
}

class SantaClausEx:SantaClaus 
{
    //provide a constructor with param names matching property names
    public SantaClausEx(string name) : base(name)
}

Json.net 不能反序列化 SantaClaus,但它可以反序列化 SantaClauseEx。

我在任何地方都使用 SantaClauseEx 类,它工作得很好,但我想制作一个转换器来自动执行此操作。

这是集合中 Json 的样子:

SantaClausCollection: [
{
  $type: "SantaClaus, XMasClasses.NET20"
  Name: "St. Bob"
},
{
  $type: "SantaClaus, XMasClasses.NET20"
  Name: "St. Jim"
}
]
4

3 回答 3

1

我想您已将转换器添加到Converters设置对象的集合中。

我用可以工作的转换器编写了简单的测试

public class SantaClausJsonTest
{
    public SantaClausJsonTest()
    {
        Settings = new JsonSerializerSettings();
        Settings.TypeNameHandling = TypeNameHandling.Objects;
        Settings.Converters.Add(new SantaClaus2JsonConverter());
    }

    private JsonSerializerSettings Settings;

    [Fact]
    public void SerializeAndDeserialize()
    {
        var collection = new []
            {
                new SantaClaus("St. Bob"),
                new SantaClaus("St. Jim"),
            };

        var serialized = JsonConvert.SerializeObject(collection, Settings);

        Console.WriteLine(serialized);
        Assert.False(string.IsNullOrEmpty(serialized));

        var deserialized = JsonConvert.DeserializeObject<SantaClaus[]>(serialized, Settings);

        Console.WriteLine(deserialized.GetType().ToString());
        Assert.NotNull(deserialized);
        Assert.True(deserialized.Any(a => a.Name == "St. Bob"));
        Assert.True(deserialized.Any(a => a.Name == "St. Jim"));
    }
}

public class SantaClaus
{
    public SantaClaus(string santaClauseName)
    {
        Name = santaClauseName;
    }

    public string Name { get; private set; }
}

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

    /// <summary>
    /// Deserializes a SantaClaus as a SantaClausEx which has a matching constructor that allows it to deserialize naturally.
    /// </summary>       
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var name = string.Empty;

        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.String && reader.Path.EndsWith("Name"))
            {
                name = reader.Value as string;
            }
            if (reader.TokenType == JsonToken.EndObject)
            {
                break;
            }
        }

        return Activator.CreateInstance(objectType, name);
    }

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


    public override bool CanRead
    {
        get
        {
            return true;
        }
    }

    public override bool CanWrite
    {
        get
        {
            return false;//We only need this converter when reading.
        }
    }
于 2013-11-26T22:06:17.493 回答
1

我在反序列化从基类继承的对象时遇到了类似的问题(类似于您需要反序列化 SantaClauseEx 对象的方式,但它们都被定义为 SantaClause 对象)。问题在于 JSon.Net 无法识别子类型。

请参阅stackoverflow.com/questions/8030538/how-to-implement-custom-jsonconverter-in-json-net-to-deserialize-a-list-of-base

于 2013-11-26T21:01:27.640 回答
0

一旦我得到了 Rudus 的答案,我就用我最初的尝试确定了这个问题。当你有一个没有默认构造函数的类型时,他很棒,但可以将属性值映射到它的其他构造函数之一,并且对于我的具体情况当然更容易。

如果由于某种原因你确实需要像我最初尝试做的那样在反序列化时创建不同类型的东西,我就能让它工作。

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

    /// <summary>
    /// Deserializes a SantaClaus as a SantaClausEx which has a matching constructor that allows it to deserialize naturally.
    /// </summary>       
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //temporarily switch off name handling so it ignores "SantaClaus" type when
        //explicitely deserialize as SantaClausEx
        //This could cause issues with nested types however in a more complicated object graph
        var temp = serializer.TypeNameHandling;
        serializer.TypeNameHandling = TypeNameHandling.None;
        var desr = serializer.Deserialize<SantaClausEx>(reader);
        serializer.TypeNameHandling = temp;//restore previous setting

        return desr;
    }

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

    public override bool CanRead { get { return true; } }

    public override bool CanWrite { get { false; } } //only for reading

}
于 2013-11-26T23:37:47.787 回答