JSON.Net not calling CanConvert for collection item?

你。 提交于 2020-01-13 10:16:30

问题


I have a converter that I only want used when deserializing. So I set CanWrite to false, which works fine and everything Serializes fine. The Json string then contains an object graph within which there is a SantaClauseCollection with an array of SantaClause items and a $type indicating they are concrete type SantaClause.

However, when it encounters a collection of SantaClaus while deserializing, it never calls CanConvert(I have a break point and see the SantaClausCollection, hit F5 to continue, which should then hit the break point again when encountering an item in the collection of SantaClaus, but it doesn't). It's not trying to call CanConvert when it gets to the SantaClaus item. Without even calling CanConvert for that item to check if my converter will handle it, it instead tries to deserialize it itself, which won't work because the class has no default constructor and no constructor with property-name matching conventions:

Unable to find a constructor to use for type SantaClaus. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute.

I understand why I get this error, but the problem is it indicates that Json.net tried to deserialize the object, instead of calling CanConvert to check and see if my converter wanted to handle the deserialization instead.

Why is CanConvert not being called for each item in the collection?

My converter:

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 is just inherits from SantaClaus to add a constructor with a renamed parameter to match properties:

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 can't deserialize a SantaClaus, but it can deserialize a SantaClauseEx.

I use that SantaClauseEx class everywhere and it works just fine, but I wanted to make a converter to do this automatically.

This is what the Json looks like for the collection:

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

回答1:


I suppose that you added your converter to Converters collection in settings object.

I wrote simple test with converter which works

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.
        }
    }



回答2:


I had a similar issue deserializing objects inherited from a base class ( similar to how you need to deserialize a SantaClauseEx object but they are all defined as SantaClause objects). The issue is in JSon.Net not being able to identify the subtype.

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




回答3:


Once I got Rudus's answer working, I identified the issue with my original attempt. His is great when you have a type with no default constructor, but can map property values to one of its other constructors and is certainly easier for my specific case.

If for some reason you really do need something like what I was originally trying to do where you create a different type when deserializing, I was able to get that working.

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

}


来源:https://stackoverflow.com/questions/14887389/json-net-not-calling-canconvert-for-collection-item

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!