5 votes

JSON.Net n'appelle pas CanConvert pour un élément de collection ?

J'ai un convertisseur que je ne veux utiliser que lors de la désérialisation. J'ai donc mis CanWrite à false, ce qui fonctionne bien et tout se sérialise correctement. La chaîne Json contient alors un graphe d'objets à l'intérieur duquel il y a une SantaClauseCollection avec un tableau d'éléments SantaClause et un $type indiquant qu'ils sont de type concret SantaClause.

Cependant, lorsqu'il rencontre une collection de SantaClaus pendant la désérialisation, il n'appelle jamais CanConvert (j'ai un point d'arrêt et je vois la collection de SantaClaus, j'appuie sur F5 pour continuer, qui devrait ensuite atteindre le point d'arrêt à nouveau lorsqu'il rencontre un élément dans la collection de SantaClaus, mais ce n'est pas le cas). Il n'essaie pas d'appeler CanConvert lorsqu'il arrive à l'élément SantaClaus. Sans même appeler CanConvert pour cet élément afin de vérifier si mon convertisseur peut le gérer, il essaie de le désérialiser lui-même, ce qui ne fonctionnera pas parce que la classe n'a pas de constructeur par défaut ni de constructeur avec des conventions de correspondance entre les noms de propriétés :

Impossible de trouver un constructeur à utiliser pour le type SantaClaus. Une classe doit avoir un constructeur par défaut, un constructeur avec des ou un constructeur marqué par l'attribut JsonConstructor.

Je comprends pourquoi j'obtiens cette erreur, mais le problème est qu'elle indique que Json.net a essayé de désérialiser l'objet, au lieu d'appeler CanConvert pour vérifier et voir si mon convertisseur voulait gérer la désérialisation à la place.

Pourquoi CanConvert n'est-il pas appelé pour chaque élément de la collection ?

Mon convertisseur :

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 hérite simplement de SantaClaus pour ajouter un constructeur avec un paramètre renommé pour correspondre aux propriétés :

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 ne peut pas désérialiser un SantaClaus, mais il peut désérialiser un SantaClauseEx.

J'utilise cette classe SantaClauseEx partout et cela fonctionne très bien, mais je voulais créer un convertisseur pour faire cela automatiquement.

Voici à quoi ressemble le Json pour la collection :

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

1voto

BgRva Points 413

J'ai eu un problème similaire en désérialisant des objets hérités d'une classe de base (similaire à la façon dont vous avez besoin de désérialiser un objet SantaClauseEx mais ils sont tous définis comme des objets SantaClause). Le problème réside dans le fait que JSon.Net n'est pas capable d'identifier le sous-type.

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

1voto

Rudis Points 942

Je suppose que vous avez ajouté votre convertisseur à Converters dans l'objet des paramètres.

J'ai écrit un test simple avec un convertisseur qui fonctionne

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

0voto

AaronLS Points 12720

Une fois que j'ai réussi à faire fonctionner la réponse de Rudus, j'ai identifié le problème de ma première tentative. La réponse de Rudus est idéale lorsque vous avez un type qui n'a pas de constructeur par défaut, mais qui peut faire correspondre les valeurs des propriétés à l'un de ses autres constructeurs, et elle est certainement plus facile à utiliser dans mon cas particulier.

Si, pour une raison quelconque, vous avez vraiment besoin de quelque chose comme ce que j'essayais de faire à l'origine, où vous créez un type différent lors de la désérialisation, j'ai pu le faire fonctionner.

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

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X