Faire en sorte que JsonConverter personnalisé respecte ItemTypeNameHandling lors de la sérialisation avec JSON.NET

J’ai un object avec une liste de sous-objects de classe de base. Les sous-objects ont besoin d’un convertisseur personnalisé. Je ne parviens pas à faire en sorte que mon convertisseur personnalisé respecte l’option ItemTypeNameHandling .

Exemple de code (créez un nouveau projet de console C #, ajoutez le package JSON.NET NuGet):

 using System; using System.Collections.Generic; using Newtonsoft.Json; namespace My { class Program { private static void Main () { Console.WriteLine(JsonConvert.SerializeObject( new Box { toys = { new Spintop(), new Ball() } }, Formatting.Indented)); Console.ReadKey(); } } [JsonObject] class Box { [JsonProperty ( ItemConverterType = typeof(ToyConverter), ItemTypeNameHandling = TypeNameHandling.Auto)] public List toys = new List(); } [JsonObject] class Toy {} [JsonObject] class Spintop : Toy {} [JsonObject] class Ball : Toy {} class ToyConverter : JsonConverter { public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return serializer.Deserialize(reader, objectType); } public override bool CanConvert (Type objectType) { return typeof(Toy).IsAssignableFrom(objectType); } } } 

Production produite:

 { "toys": [ {}, {} ] } 

Sortie nécessaire (c’est ce qui arrive si je commente ItemConverterType = typeof(ToyConverter), line):

 { "toys": [ { "$type": "My.Spintop, Serialization" }, { "$type": "My.Ball, Serialization" } ] } 

J’ai essayé de changer temporairement la valeur de serializer.TypeNameHandling dans la méthode ToyConverter.WriteJson , mais cela affecte des propriétés non liées. (Bien entendu, mon vrai convertisseur est plus complexe que cela. Ce n’est qu’un exemple avec des fonctionnalités de base.)

Question: Comment faire en sorte que mon JsonConverter personnalisé respecte la propriété JsonProperty atsortingbut JsonProperty ?

Après avoir exploré le code source de Json.Net (version 4.5 édition 11), il semble que ce que vous voulez faire ne soit pas possible.

La clé pour obtenir des types écrits dans la sortie est cette méthode:

 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter .ShouldWriteType(TypeNameHandling typeNameHandlingFlag, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty) 

Ce sont les parameters containerContract et containerProperty qui sont importants ici. Lors de la sérialisation sans convertisseur, ces parameters sont fournis et ShouldWriteType peut les utiliser pour déterminer le TypeNameHandling à utiliser.

Lors de la sérialisation avec un convertisseur, cependant, ces deux parameters ne sont pas fournis. Cela semble être dû au fait que ToyConverter.WriteJson aboutit à un appel à Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue comme Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue :

 SerializeValue(jsonWriter, value, GetContractSafe(value), null, null, null); 

Notez que les deux derniers parameters sont en fait JsonContainerContract containerContract et JsonProperty containerProperty et sont transmis à la méthode ShouldWriteType . C’est là que réside le problème: comme ils sont nuls, la logique de la méthode ShouldWriteType signifie qu’elle renvoie false , le type n’est donc pas écrit.

Modifier:

Inspiré par cela , vous pouvez résoudre ce problème en personnalisant la méthode WriteJson de votre convertisseur:

 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteStartObject(); writer.WritePropertyName("$type"); writer.WriteValue(RemoveAssemblyDetails(value.GetType().AssemblyQualifiedName.ToSsortingng())); writer.WriteEndObject(); } private static ssortingng RemoveAssemblyDetails(ssortingng fullyQualifiedTypeName) { SsortingngBuilder builder = new SsortingngBuilder(); // loop through the type name and filter out qualified assembly details from nested type names bool writingAssemblyName = false; bool skippingAssemblyDetails = false; for (int i = 0; i < fullyQualifiedTypeName.Length; i++) { char current = fullyQualifiedTypeName[i]; switch (current) { case '[': writingAssemblyName = false; skippingAssemblyDetails = false; builder.Append(current); break; case ']': writingAssemblyName = false; skippingAssemblyDetails = false; builder.Append(current); break; case ',': if (!writingAssemblyName) { writingAssemblyName = true; builder.Append(current); } else { skippingAssemblyDetails = true; } break; default: if (!skippingAssemblyDetails) builder.Append(current); break; } } return builder.ToString(); } 

Notez que cette méthode RemoveAssemblyDetails est extraite directement de la source Json.Net.

Vous devrez bien sûr modifier la méthode WriteJson pour afficher le rest des champs, mais j'espère que cela suffira.