J'ai une hiérarchie de classes simple que je souhaite sérialiser à l'aide de System.Text.Json.
Il y a 3 classes. La base est Shape
. Les héritiers sont Box
et Circle
.
J'ai un plan pour utiliser ces classes comme une union étiquetée sur mon application frontend, je viens donc d'introduire une propriété de discriminateur Tag
.
J'ai écrit un convertisseur de type qui prend en charge la sérialisation / désérialisation de cette hiérarchie.
Ce que j'essaie de comprendre - est-ce une meilleure approche pour implémenter une telle fonctionnalité ou non. En effet le résultat de sortie de la sérialisation est assez moche (j'ai mis un commentaire dans un exemple ci-dessous). Je ne suis pas sûr que ce soit fait de la meilleure façon, de toute façon, cela fonctionne.
Voici l'exemple comment j'ai implémenté la sérialisation / désérialisation:
using System; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; namespace Serialization.Theory { public abstract class Shape { public abstract String Tag { get; } } public class Box : Shape { public override String Tag { get; } = nameof(Box); public Single Width { get; set; } public Single Height { get; set; } public override String ToString() { return $"{Tag}: Width={Width}, Height={Height}"; } } public class Circle : Shape { public override String Tag { get; } = nameof(Circle); public Single Radius { get; set; } public override String ToString() { return $"{Tag}: Radius={Radius}"; } } public class ShapeConverter : JsonConverter<Shape> { public override Boolean CanConvert(Type typeToConvert) { return typeToConvert == typeof(Circle) || typeToConvert == typeof(Shape); } public override Shape Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var raw = reader.GetString(); var doc = JsonDocument.Parse(raw); var prop = doc.RootElement.EnumerateObject().Where(x => x.Name == "Tag").First(); var value = prop.Value.GetString(); switch (value) { case nameof(Circle): return JsonSerializer.Deserialize<Circle>(raw); case nameof(Box): return JsonSerializer.Deserialize<Box>(raw); default: throw new NotSupportedException(); } } public override void Write(Utf8JsonWriter writer, Shape value, JsonSerializerOptions options) { if (value is Circle circle) { writer.WriteStringValue(JsonSerializer.SerializeToUtf8Bytes(circle)); } else if (value is Box box) { writer.WriteStringValue(JsonSerializer.SerializeToUtf8Bytes(box)); } } } class Program { static void Main(string[] args) { // Keep in base class references like it's a property on another object. Shape origin1 = new Box { Width = 10, Height = 20 }; Shape origin2 = new Circle { Radius = 30 }; var settings = new JsonSerializerOptions(); settings.Converters.Add(new ShapeConverter()); var raw1 = JsonSerializer.Serialize(origin1, settings); var raw2 = JsonSerializer.Serialize(origin2, settings); Console.WriteLine(raw1); // "{\u0022Tag\u0022:\u0022Box\u0022,\u0022Width\u0022:10,\u0022Height\u0022:20}" Console.WriteLine(raw2); // "{\u0022Tag\u0022:\u0022Circle\u0022,\u0022Radius\u0022:30}" var restored1 = JsonSerializer.Deserialize<Shape>(raw1, settings); var restored2 = JsonSerializer.Deserialize<Shape>(raw2, settings); Console.WriteLine(restored1); // Box: Width=10, Height=20 Console.WriteLine(restored2); // Circle: Radius=30 } } }
3 Réponses :
J'ai également besoin d'une réponse. Il semble qu'un autre article en parle mais ne donne pas une bonne réponse. Voir aussi cette discussion .
Semble que System.Text.Json a une API de niveau assez bas. Je suis passé à Newtonsoft JSON mais je vais continuer à regarder comment System.Text.Json évolue.
Espérons que cette réponse sur le fil de discussion que vous avez lié à @ nitro911 vous aidera: stackoverflow.com/a/59744873/12509023
Veuillez essayer cette bibliothèque que j'ai écrite en tant qu'extension de System.Text.Json pour offrir le polymorphisme: https://github.com/dahomey-technologies/Dahomey.Json
JsonSerializerOptions options = new JsonSerializerOptions(); options.SetupExtensions(); DiscriminatorConventionRegistry registry = options.GetDiscriminatorConventionRegistry(); registry.RegisterConvention(new AttributeBasedDiscriminatorConvention<string>(options, "Tag")); registry.RegisterType<Box>(); registry.RegisterType<Circle>(); Shape origin1 = new Box { Width = 10, Height = 20 }; Shape origin2 = new Circle { Radius = 30 }; string json1 = JsonSerializer.Serialize(origin1, options); string json2 = JsonSerializer.Serialize(origin2, options); Console.WriteLine(json1); // {"Tag":"Box","Width":10,"Height":20} Console.WriteLine(json2); // {"Tag":"Circle","Radius":30} var restored1 = JsonSerializer.Deserialize<Shape>(json1, options); var restored2 = JsonSerializer.Deserialize<Shape>(json2, options); Console.WriteLine(restored1); // Box: Width=10, Height=20 Console.WriteLine(restored2); // Circle: Radius=30
Les classes héritées doivent être enregistrées manuellement dans le registre de convention de discriminateur afin d'informer le framework du mappage entre une valeur de discriminateur et un type:
public abstract class Shape { } [JsonDiscriminator(nameof(Box))] public class Box : Shape { public float Width { get; set; } public float Height { get; set; } public override string ToString() { return $"Box: Width={Width}, Height={Height}"; } } [JsonDiscriminator(nameof(Circle))] public class Circle : Shape { public float Radius { get; set; } public override string ToString() { return $"Circle: Radius={Radius}"; } }
Cela a bien fonctionné pour moi (dans .Net 5):
JsonSerializer.Serialize<object>(myInheritedObject)
Ensuite, toutes les propriétés de la classe de base et héritée ont été incluses. Je ne sais pas s'il y a des problèmes avec cette approche cependant ...
@shadeglare, veuillez voir la réponse dans la question associée (qui pourrait être plus propre): stackoverflow.com/a/59744873/12509023 La (dé) sérialisation polymorphe n'est pas prise en charge et nécessite un convertisseur similaire à ce que vous avez écrit. Il y a un problème pour l'ajout de la prise en charge de la sérialisation polymorphe en tant qu'opt -in: github.com/dotnet/corefx/issues/38650
Vous devez disposer votre
JsonDocument
pour éviter une fuite de mémoire, par exemple enusing var doc = JsonDocument.Parse(raw);
Voir les remarques du document pour plus de détails.