{"id":726,"date":"2020-12-04T18:40:10","date_gmt":"2020-12-04T23:40:10","guid":{"rendered":"https:\/\/badecho.com\/?p=726"},"modified":"2022-02-22T00:39:04","modified_gmt":"2022-02-22T05:39:04","slug":"polymorphic-json-deserialization","status":"publish","type":"post","link":"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/","title":{"rendered":"Polymorphic Deserialization With System.Text.Json in .NET 5.0"},"content":{"rendered":"\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_11 counter-hierarchy counter-decimal ez-toc-grey\">\r\n<div class=\"ez-toc-title-container\">\r\n<p class=\"ez-toc-title\">Table of Contents<\/p>\r\n<span class=\"ez-toc-title-toggle\"><a class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\"><i class=\"ez-toc-glyphicon ez-toc-icon-toggle\"><\/i><\/a><\/span><\/div>\r\n<nav><ul class=\"ez-toc-list ez-toc-list-level-1\"><li class=\"ez-toc-page-1 ez-toc-heading-level-2\"><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#New_Technology_and_the_Need_to_Deserialize_JSON\" title=\"New Technology and the Need to Deserialize JSON\">New Technology and the Need to Deserialize JSON<\/a><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-2\"><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#Saying_Bye_to_the_Third_Party,_Then_Missing_Them\" title=\"Saying Bye to the Third Party, Then Missing Them\">Saying Bye to the Third Party, Then Missing Them<\/a><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-2\"><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#The_Data_We%E2%80%99ll_Be_Working_With\" title=\"The Data We&#8217;ll Be Working With\">The Data We&#8217;ll Be Working With<\/a><ul class=\"ez-toc-list-level-3\"><li class=\"ez-toc-heading-level-3\"><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#The_Statistic_Objects\" title=\"The Statistic Objects\">The Statistic Objects<\/a><ul class=\"ez-toc-list-level-4\"><li class=\"ez-toc-heading-level-4\"><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#Base_Statistic_Class\" title=\"Base Statistic Class\">Base Statistic Class<\/a><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-4\"><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#WholeStatistic_Class\" title=\"WholeStatistic Class\">WholeStatistic Class<\/a><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-4\"><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#FractionalStatistic_Class\" title=\"FractionalStatistic Class\">FractionalStatistic Class<\/a><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-4\"><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#CoordinateStatistic_Class\" title=\"CoordinateStatistic Class\">CoordinateStatistic Class<\/a><\/li><\/ul><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-3\"><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#Sample_JSON_Game_Data\" title=\"Sample JSON Game Data\">Sample JSON Game Data<\/a><ul class=\"ez-toc-list-level-4\"><li class=\"ez-toc-heading-level-4\"><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#stats_json\" title=\"stats.json\">stats.json<\/a><\/li><\/ul><\/li><\/ul><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-2\"><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#Polymorphic_Deserialization_With_a_Base_Converter\" title=\"Polymorphic Deserialization With a Base Converter\">Polymorphic Deserialization With a Base Converter<\/a><ul class=\"ez-toc-list-level-3\"><li class=\"ez-toc-heading-level-3\"><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#JsonPolymorphicConverter_Class\" title=\"JsonPolymorphicConverter&lt;TTypeDescriptor,TBase&gt; Class\">JsonPolymorphicConverter&lt;TTypeDescriptor,TBase&gt; Class<\/a><\/li><\/ul><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-2\"><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#Creating_the_Omnified_Statistics_Converter\" title=\"Creating the Omnified Statistics Converter\">Creating the Omnified Statistics Converter<\/a><ul class=\"ez-toc-list-level-3\"><li class=\"ez-toc-heading-level-3\"><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#StatisticType_Enum\" title=\"StatisticType Enum\">StatisticType Enum<\/a><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-3\"><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#StatisticConverter_Class\" title=\"StatisticConverter Class\">StatisticConverter Class<\/a><\/li><li class=\"ez-toc-page-1 ez-toc-heading-level-3\"><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#Sample_Usage\" title=\"Sample Usage\">Sample Usage<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\r\n<h2><span class=\"ez-toc-section\" id=\"New_Technology_and_the_Need_to_Deserialize_JSON\"><\/span>New Technology and the Need to Deserialize JSON<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>As part of the ongoing development effort to create <em>Statsplash<\/em>, an <a href=\"https:\/\/badecho.com\/index.php\/what-is-omnified\/\" target=\"_blank\" rel=\"noreferrer noopener\">Omnified<\/a> technology that creates a screen overlay showing raw, data mined game statistics for display on my <a href=\"https:\/\/twitch.tv\/omni\" target=\"_blank\" rel=\"noreferrer noopener\">stream<\/a>, I needed to formalize a type of data contract and method for communicating raw game data from hacked assembly code to higher .NET level processes.<\/p>\n\n\n\n<p>When deciding on which data format to use, it made sense to use JSON; something much simpler and faster than XML. Basically everything is using it these days, all the hip and cool kids included.<\/p>\n\n\n\n<p>Because official support for dealing with JSON can now be found in .NET in the <code><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.text.json?view=net-5.0\" target=\"_blank\" rel=\"noreferrer noopener\">System.Text.Json<\/a><\/code> namespace, I decided to make use of that to turn data exported from a hacked game process into beautiful and coherent (in comparison to the horrors of raw memory one faces when working with assembly) .NET objects.<\/p>\n\n\n\n<p>I&#8217;ll be writing a number of <a href=\"https:\/\/badecho.com\/index.php\/category\/omnified-design\/\" target=\"_blank\" rel=\"noreferrer noopener\">Omnified Design<\/a> articles detailing the experiences I&#8217;ve had with the creation of the <em>Statsplash <\/em>system, however for this article I just wanted to share a problem I had with a &#8220;shortcoming&#8221; of the <code>System.Text.Json<\/code> namespace and what my solution was.<\/p>\n\n\n\n<h2><span class=\"ez-toc-section\" id=\"Saying_Bye_to_the_Third_Party,_Then_Missing_Them\"><\/span>Saying Bye to the Third Party, Then Missing Them<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Since .NET 5.0 just came out, at the time of writing at least, it seemed like an appropriate time to port my army of very useful <strong>Bad Echo<\/strong> <strong>.NET libraries<\/strong> to this latest and seemingly important release of .NET.<\/p>\n\n\n\n<p>When creating these universal .NET libraries (basically libraries I will use in anything I write, as they solve so many general-purpose problems), I like to keep third party dependencies to a minimum. Since .NET had official support for JSON now, that means no more reliance on ye olde <a href=\"https:\/\/www.newtonsoft.com\/json\" target=\"_blank\" rel=\"noreferrer noopener\">Json.NET<\/a>, from Newtonsoft. <\/p>\n\n\n\n<p>In years past, if you wanted to deal with JSON in your .NET application, you had to grab ahold of that third party library and reference it. Well, now we apparently don&#8217;t need to, so I went ahead and:<\/p>\n\n\n\n<ol><li>Constructed a JSON schema that the hacked assembly code would follow in writing the data we mined, and<\/li><li>Wrote some code in <em>Statsplash <\/em>using <code>System.Text.Json<\/code> to deserialize the JSON output into a hierarchy of objects deriving from a <code>Statistic<\/code> base type.<\/li><\/ol>\n\n\n\n<p><strong><em>Fail.<\/em><\/strong><\/p>\n\n\n\n<p>If you take a gander over at the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/serialization\/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0\" target=\"_blank\" rel=\"noreferrer noopener\">listing of differences between the two libraries<\/a> by Microsoft, you will see that polymorphic deserialization and serialization are both not supported out of the box. A workaround is provided, which requires you to write your own converter deriving from <code>System.Text.Json.Serialization.JsonConverter&lt;T&gt;<\/code>.<\/p>\n\n\n\n<p>This workaround easily ends up being a lot of work however, and there&#8217;s is really no &#8220;generic&#8221; solution provided.<\/p>\n\n\n\n<p>The lack of support for polymorphic serialization isn&#8217;t due to any sort of ineptitude on Microsoft&#8217;s part. It was actually quite <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/30969#issuecomment-535779492\" target=\"_blank\" rel=\"noreferrer noopener\">intentionally left out<\/a>, as allowing for the data to be solely in control of its own type instantiation is indeed quite a potential security threat.<\/p>\n\n\n\n<p>When using Newtonsoft&#8217;s libraries, one would add support for polymorphic deserialization by storing type information for a JSON object in a property named <code>$type<\/code>. Technically, the data could have us instantiating whatever type from whatever assembly it wanted! <\/p>\n\n\n\n<p>Of course, one can tighten down on these security concerns by providing an appropriate <code>ISerializationBinder<\/code> instance, which adds a measure of control to what types are actually going to be initialized. That&#8217;s good to know if you are set on using Newtonsoft&#8217;s stuff, but I was more interested in seeing if we could get some use out of the new <code>System.Text.Json<\/code> in my software.<\/p>\n\n\n\n<p>Further into the GitHub discussion linked above, the same .NET developer provides <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/30969#issuecomment-536343793\" target=\"_blank\" rel=\"noreferrer noopener\">an acceptable solution<\/a> that would avoid the potential security pitfalls encountered when letting data have a say in its own type information. Specifically, separating the type information from the payload, and then only allowing type information to be expressed as an integer that the controlling serialization program would have mapped to a constrained list of types.<\/p>\n\n\n\n<p>In order to get <em>Statsplash <\/em>to read our JSON game data while appeasing the .NET gods, we&#8217;re going to create a base converter type that does exactly that. The universal library in the Bad Echo ecosystem is the <strong>BadEcho.Common.dll<\/strong> assembly, which comprises the Bad Echo core frameworks; general purpose .NET constructions and helper functionalities I get so much use out of. <\/p>\n\n\n\n<p>So, we&#8217;ll be stuffing it in there and then referencing that from <em>Statsplash<\/em>.<\/p>\n\n\n\n<h2><span class=\"ez-toc-section\" id=\"The_Data_We%E2%80%99ll_Be_Working_With\"><\/span>The Data We&#8217;ll Be Working With<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Before we get into <em>how<\/em> we&#8217;ll be reading the data from JSON, let&#8217;s go over quickly <em>what<\/em> we&#8217;ll be reading from JSON, as well as a sample of some JSON that will be outputted by our injected Omnified assembly code.<\/p>\n\n\n\n<p>All of these data types are native to the <em>Statsplash<\/em> project; be aware that these types as they are presented here should all be considered as preliminary. They will undoubtedly undergo changes prior to the release of <em>Statsplash<\/em>.<\/p>\n\n\n\n<h3><span class=\"ez-toc-section\" id=\"The_Statistic_Objects\"><\/span>The Statistic Objects<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p>The data of interest collected from our injected code take the form of individual (typically numeric) statistics, each of which we&#8217;ll be displaying to the viewer in one form or another.<\/p>\n\n\n\n<h4><span class=\"ez-toc-section\" id=\"Base_Statistic_Class\"><\/span>Base Statistic Class<span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides an individual statistic exported from an Omnified game.\n\/\/\/ &lt;\/summary&gt;\npublic abstract class Statistic\n{\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets or sets the display name of the statistic.\n    \/\/\/ &lt;\/summary&gt;\n    public string Name\n    { get; set; } = string.Empty;\n}\n<\/pre>\n\n\n<p>This is the root of all the various objects we&#8217;ll be deserializing from JSON, and is indeed the base type we&#8217;ll be setting up our converter to work with.<\/p>\n\n\n\n<h4><span class=\"ez-toc-section\" id=\"WholeStatistic_Class\"><\/span>WholeStatistic Class<span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides an individual statistic exported from an Omnified game concerning a whole, numeric value.\n\/\/\/ &lt;\/summary&gt;\npublic sealed class WholeStatistic : Statistic\n{\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets or sets the whole numeric value for the statistic.\n    \/\/\/ &lt;\/summary&gt;\n    public int Value\n    { get; set; }\n}\n<\/pre>\n\n\n<p>The first specific statistic type is also the simplest. It is a single number, and will be used to express statistics such as &#8220;Number of Deaths&#8221; or &#8220;Number of Souls&#8221;, etc.<\/p>\n\n\n\n<h4><span class=\"ez-toc-section\" id=\"FractionalStatistic_Class\"><\/span>FractionalStatistic Class<span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides an individual statistic exported from an Omnified game concerning a fractional,\n\/\/\/ numeric value.\n\/\/\/ &lt;\/summary&gt; \npublic sealed class FractionalStatistic : Statistic\n{\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets or sets the current numeric value for the statistic.\n    \/\/\/ &lt;\/summary&gt;\n    public int CurrentValue\n    { get; set; }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets or sets the maximum numeric value the statistic can be.\n    \/\/\/ &lt;\/summary&gt;\n    public int MaximumValue\n    { get; set; }\n}\n<\/pre>\n\n\n<p>This next type of statistic is probably the most commonly used one for expressing information pertaining to the player or another character. It is a fractional value, where the &#8220;denominator&#8221; represents the maximum value of a stat, and the the &#8220;numerator&#8221; represents the current value. <\/p>\n\n\n\n<p>An example of this is the player&#8217;s health; typically, there is a maximum health amount known, and then of course the current health, like so:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Player Health: 1230\/1400<\/code><\/pre>\n\n\n\n<p>There are of course, innumerable other types of information that are fractional in nature: stamina, mana pool, etc.<\/p>\n\n\n\n<h4><span class=\"ez-toc-section\" id=\"CoordinateStatistic_Class\"><\/span>CoordinateStatistic Class<span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides an individual statistic exported from an Omnified game concerning a coordinate\n\/\/\/ triplet value.\n\/\/\/ &lt;\/summary&gt;\npublic sealed class CoordinateStatistic : Statistic\n{\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets or sets the value for the X coordinate.\n    \/\/\/ &lt;\/summary&gt;\n    public float X\n    { get; set; }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets or sets the value for the Y coordinate.\n    \/\/\/ &lt;\/summary&gt;\n    public float Y\n    { get; set; }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets or sets the value for the Z coordinate.\n    \/\/\/ &lt;\/summary&gt;\n    public float Z\n    { get; set; }\n}\n<\/pre>\n\n\n<p>This final type of statistic object, at least as far as this article is concerned, deals with Cartesian coordinate triplets. This is how the location of all types of entities is described in your typical 3D game.<\/p>\n\n\n\n<p>The triplet consists of X, Y, and Z axis coordinate values, and because they are typically always floats in the game&#8217;s memory, they are represented as floats here in the class. I know there&#8217;s a tendency to use <code>double<\/code> in .NET code for any non-integer number (well, it is the default type for such a thing), but they are all <code>float<\/code> here because that&#8217;s what they actually are.<\/p>\n\n\n\n<p>So these objects are basically what we&#8217;ll be wanting to produce from the JSON, in the form of an <code>IEnumerable&lt;Statistic&gt;<\/code> instance. Time to take a look at what kind data we&#8217;ll actually be deserializing.<\/p>\n\n\n\n<h3><span class=\"ez-toc-section\" id=\"Sample_JSON_Game_Data\"><\/span>Sample JSON Game Data<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p>Our injected assembly code will be doing its thing and outputting mined game data to a statistics JSON file, and it&#8217;ll look a little something like this:<\/p>\n\n\n\n<h4><span class=\"ez-toc-section\" id=\"stats_json\"><\/span>stats.json<span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;\n  {\n    \"Type\": 1,\n    \"Statistic\": {\n      {\n        \"CurrentValue\": 1700,\n        \"MaximumValue\": 2045,\n        \"Name\": \"Player Health\"\n      }\n    }\n  },\n  {\n    \"Type\": 1,\n    \"Statistic\": {\n      {\n        \"CurrentValue\": 3520,\n        \"MaximumValue\": 5000,\n        \"Name\": \"Enemy Health\"\n      }\n    }\n  },\n  {\n    \"Type\": 0,\n    \"Statistic\": {\n      {\n        \"Value\": 120,\n        \"Name\": \"Last Damage Taken\"\n      }\n    }\n  },\n  {\n    \"Type\": 0,\n    \"Statistic\": {\n      {\n        \"Value\": 50000,\n        \"Name\": \"Max Damage Taken\"\n      }\n    }\n  },\n  {\n    \"Type\": 0,\n    \"Statistic\": {\n      {\n        \"Value\": 50120,\n        \"Name\": \"Total Damage Taken\"\n      }\n    }\n  },\n  {\n    \"Type\": 0,\n    \"Statistic\": {\n      {\n        \"Value\": 200,\n        \"Name\": \"Last Damage Done\"\n      }\n    }\n  },\n  {\n    \"Type\": 0,\n    \"Statistic\": {\n      {\n        \"Value\": 400,\n        \"Name\": \"Max Damage Done\"\n      }\n    }\n  },\n  {\n    \"Type\": 0,\n    \"Statistic\": {\n      {\n        \"Value\": 2000,\n        \"Name\": \"Total Damage Done\"\n      }\n    }\n  },\n  {\n    \"Type\": 2,\n    \"Statistic\": {\n      {\n        \"X\": -1566.7635,\n        \"Y\": 0.10857524,\n        \"Z\": 1997.6492,\n        \"Name\": \"Coordinates\"\n      }\n    }\n  }\n]<\/code><\/pre>\n\n\n\n<p>The actual Omnified hacking code used to achieve the above output is outside the scope of the article, and will be provided when we actually dig deep into <em>Statsplash<\/em> upon its release.<\/p>\n\n\n\n<p>So, looking at the above JSON, we can see an array containing our various statistic objects, with the type information and payload data separated for each one.<\/p>\n\n\n\n<p>Let&#8217;s get to creating a general purpose polymorphic converter using Microsoft&#8217;s new libraries, and then a derived one showing how to use it to serialize these objects.<\/p>\n\n\n\n<h2><span class=\"ez-toc-section\" id=\"Polymorphic_Deserialization_With_a_Base_Converter\"><\/span>Polymorphic Deserialization With a Base Converter<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>So how to support working with data like the above JSON snippet with Microsoft&#8217;s new <code>System.Text.Json<\/code> namespace? Well we&#8217;ll start off like they say to do and make a class deriving from <code>JsonConverter&lt;T&gt;<\/code>.<\/p>\n\n\n\n<p>This new class is going to itself be a base class that will require a deriving class to get any use out of it with specific datasets. This new mystery class shall heretofore be known as the <code>JsonPolymorphicConverter&lt;TTypeDescriptor,TBase&gt;<\/code> class. <\/p>\n\n\n\n<p>The code itself is fully documented, so I hope that the code&#8217;s own documentation will ensure everything is clear to the reader.<\/p>\n\n\n\n<h3><span class=\"ez-toc-section\" id=\"JsonPolymorphicConverter_Class\"><\/span>JsonPolymorphicConverter&lt;TTypeDescriptor,TBase&gt; Class<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides a base class for converting a hierarchy of objects to or from JSON.\n\/\/\/ &lt;\/summary&gt;\n\/\/\/ &lt;typeparam name=&quot;TTypeDescriptor&quot;&gt;\n\/\/\/ A type of &lt;see cref=&quot;Enum&quot;\/&gt; whose integer value describes the type of object in JSON.\n\/\/\/ &lt;\/typeparam&gt;\n\/\/\/ &lt;typeparam name=&quot;TBase&quot;&gt;The base type of object handled by the converter.&lt;\/typeparam&gt;\npublic abstract class JsonPolymorphicConverter&lt;TTypeDescriptor,TBase&gt; : JsonConverter&lt;TBase&gt;\n    where TTypeDescriptor : Enum\n    where TBase : class\n{\n    private const string DEFAULT_TYPE_PROPERTY_NAME = &quot;Type&quot;;\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets the expected property name of the number in JSON whose\n    \/\/\/ &lt;typeparamref name=&quot;TTypeDescriptor&quot;\/&gt; representation is used to determine the specific\n    \/\/\/ type of &lt;typeparamref name=&quot;TBase&quot;\/&gt; instantiated.\n    \/\/\/ &lt;\/summary&gt;\n    protected virtual string TypePropertyName \n        =&gt; DEFAULT_TYPE_PROPERTY_NAME;\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets the expected property name of the object in JSON containing the object data payload.\n    \/\/\/ &lt;\/summary&gt;\n    protected abstract string DataPropertyName { get; }\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    public override TBase? Read(\n        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n    {\n        if (reader.TokenType != JsonTokenType.StartObject)\n            throw new JsonException(Strings.JsonExceptionNotStartObject);\n            \n        reader.Read();\n        if (reader.TokenType != JsonTokenType.PropertyName)\n            throw new JsonException(Strings.JsonExceptionMalformedText);\n            \n        string? typePropertyName = reader.GetString();\n        if (typePropertyName != TypePropertyName)\n            throw new JsonException(Strings.JsonExceptionInvalidTypeName.CulturedFormat(typePropertyName!, TypePropertyName));\n\n        reader.Read();\n        if (reader.TokenType != JsonTokenType.Number)\n            throw new JsonException(Strings.JsonExceptionTypeValueNotNumber);\n\n        var typeDescriptor = reader.GetInt32().ToEnum&lt;TTypeDescriptor&gt;();\n        reader.Read();\n\n        if (reader.TokenType != JsonTokenType.PropertyName)\n            throw new JsonException(Strings.JsonExceptionMalformedText);\n\n        string? dataPropertyName = reader.GetString();\n        if (dataPropertyName != DataPropertyName)\n            throw new JsonException(Strings.JsonExceptionInvalidTypeName.CulturedFormat(dataPropertyName!, DataPropertyName));\n\n        reader.Read();\n\n        if (reader.TokenType != JsonTokenType.StartObject)\n            throw new JsonException(Strings.JsonExceptionDataValueNotObject);\n\n        TBase? readValue = ReadFromDescriptor(ref reader, typeDescriptor);\n        reader.Read();\n            \n        return readValue;\n    }\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    public override void Write(Utf8JsonWriter writer, TBase value, JsonSerializerOptions options)\n    {\n        if (writer == null) \n            throw new ArgumentNullException(nameof(writer));\n\n        if (value == null)\n            throw new ArgumentNullException(nameof(value));\n\n        TTypeDescriptor typeDescriptor = DescriptorFromValue(value);\n\n        writer.WriteStartObject();\n            \n        writer.WriteNumber(TypePropertyName, typeDescriptor.ToInt32());\n\n        writer.WriteStartObject(DataPropertyName);\n        JsonSerializer.Serialize(writer, value, value.GetType(), options);\n        writer.WriteEndObject();\n\n        writer.WriteEndObject();\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Reads and converts the JSON to a &lt;typeparamref name=&quot;TBase&quot;\/&gt;-derived type described by the provided\n    \/\/\/ &lt;typeparamref name=&quot;TTypeDescriptor&quot;\/&gt; enumeration.\n    \/\/\/ &lt;\/summary&gt;\n    \/\/\/ &lt;param name=&quot;reader&quot;&gt;The reader, positioned at the payload data of the object to read.&lt;\/param&gt;\n    \/\/\/ &lt;param name=&quot;typeDescriptor&quot;&gt;\n    \/\/\/ An enumeration value that specifies the type of &lt;typeparamref name=&quot;TBase&quot;\/&gt; to read.\n    \/\/\/ &lt;\/param&gt;\n    \/\/\/ &lt;returns&gt;The converted value.&lt;\/returns&gt;\n    protected abstract TBase? ReadFromDescriptor(ref Utf8JsonReader reader, TTypeDescriptor typeDescriptor);\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Produces a &lt;typeparamref name=&quot;TTypeDescriptor&quot;\/&gt; value specifying a converted value's type.\n    \/\/\/ &lt;\/summary&gt;\n    \/\/\/ &lt;param name=&quot;value&quot;&gt;The converted value to create a type descriptor for.&lt;\/param&gt;\n    \/\/\/ &lt;returns&gt;\n    \/\/\/ A &lt;typeparamref name=&quot;TTypeDescriptor&quot;\/&gt; value that specifies the type of &lt;c&gt;value&lt;\/c&gt; in JSON.\n    \/\/\/ &lt;\/returns&gt;\n    protected abstract TTypeDescriptor DescriptorFromValue(TBase value);\n}\n<\/pre>\n\n\n<p>If there is any call above being made to methods that don&#8217;t compile for you, they are most likely extension methods that are part of my Bad Echo core frameworks. The source for those frameworks will be put up someday, but otherwise the actual things that these methods do can be easily figured out (although my extension methods typically are the result of long bits of research to find out the best way to do whatever it is doing).<\/p>\n\n\n\n<p>To use the above base converter class, you need to create a derived class targeting a specific base object, providing support for the target object by providing implementations for the <code>ReadFromDescriptor<\/code> and <code>DescriptorFromValue<\/code> methods.<\/p>\n\n\n\n<p>You also need to create an enumeration type that will essentially link the integer values in the JSON to type specifications.<\/p>\n\n\n\n<p>All of this is very easy to do, and we&#8217;ll show an example that uses our statistics objects now.<\/p>\n\n\n\n<h2><span class=\"ez-toc-section\" id=\"Creating_the_Omnified_Statistics_Converter\"><\/span>Creating the Omnified Statistics Converter<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>We&#8217;re going to go ahead then and first create an enumeration type that will specify the particular type of statistics object we&#8217;re working with.<\/p>\n\n\n\n<h3><span class=\"ez-toc-section\" id=\"StatisticType_Enum\"><\/span>StatisticType Enum<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Specifies the type of statistic exported from an Omnified game.\n\/\/\/ &lt;\/summary&gt;\npublic enum StatisticType\n{\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ A whole, numeric value statistic.\n    \/\/\/ &lt;\/summary&gt;\n    Whole,\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ A fractional, numeric value statistic.\n    \/\/\/ &lt;\/summary&gt;\n    Fractional,\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ A coordinate triplet value statistic.\n    \/\/\/ &lt;\/summary&gt;\n    Coordinate\n}\n<\/pre>\n\n\n<p>Very simple, this basically establishes a mapping of integer values 0, 1, and 2 to <code>Whole<\/code>, <code>Fractional<\/code>, and <code>Coordinate <\/code>respectively.<\/p>\n\n\n\n<p>Now to implement our specific statistics converter. Here you will see that the implementation of the required methods are essentially just some simple-to-write pattern matching expressions.<\/p>\n\n\n\n<h3><span class=\"ez-toc-section\" id=\"StatisticConverter_Class\"><\/span>StatisticConverter Class<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides a converter of &lt;see cref=&quot;Statistic&quot;\/&gt; objects to or from JSON.\n\/\/\/ &lt;\/summary&gt;\npublic sealed class StatisticConverter : JsonPolymorphicConverter&lt;StatisticType,Statistic&gt;\n{\n    private const string STATISTIC_DATA_PROPERTY_NAME = &quot;Statistic&quot;;\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    protected override string DataPropertyName\n        =&gt; STATISTIC_DATA_PROPERTY_NAME;\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    protected override Statistic? ReadFromDescriptor(\n        ref Utf8JsonReader reader, StatisticType typeDescriptor)\n    {\n        return typeDescriptor switch\n        {\n            StatisticType.Whole =&gt; JsonSerializer.Deserialize&lt;WholeStatistic&gt;(ref reader),\n            StatisticType.Fractional =&gt; JsonSerializer.Deserialize&lt;FractionalStatistic&gt;(ref reader),\n            StatisticType.Coordinate =&gt; JsonSerializer.Deserialize&lt;CoordinateStatistic&gt;(ref reader),\n            _ =&gt; throw new InvalidEnumArgumentException(nameof(typeDescriptor), \n                                                        (int) typeDescriptor, \n                                                        typeof(StatisticType))\n        };\n    }\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    protected override StatisticType DescriptorFromValue(Statistic value)\n    {\n        return value switch\n        {\n            WholeStatistic =&gt; StatisticType.Whole,\n            FractionalStatistic =&gt; StatisticType.Fractional,\n            CoordinateStatistic =&gt; StatisticType.Coordinate,\n            _ =&gt; throw new ArgumentException(Strings.ArgumentExceptionStatisticTypeUnsupported, \n                                             nameof(value))\n        };\n    }\n}\n<\/pre>\n\n\n<p>It&#8217;s very simple to create a derived polymorphic converter, as you can see above. Let&#8217;s wire it up with some sample code, and see what we get.<\/p>\n\n\n\n<h3><span class=\"ez-toc-section\" id=\"Sample_Usage\"><\/span>Sample Usage<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n            string jsonString = File.ReadAllText(&quot;stats.json&quot;);\n            var options = new JsonSerializerOptions {Converters = {new StatisticConverter()}};\n\n            var statistics = JsonSerializer.Deserialize&lt;IEnumerable&lt;Statistic&gt;&gt;(jsonString, options);\n<\/pre>\n\n\n<p>Running this, we get&#8230;<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" width=\"548\" height=\"282\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2020\/12\/StatisticsDeserialization.png\" alt=\"Shows the serialized objects via a debugger.\" class=\"wp-image-747\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2020\/12\/StatisticsDeserialization.png 548w, https:\/\/badecho.com\/wp-content\/uploads\/2020\/12\/StatisticsDeserialization-300x154.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2020\/12\/StatisticsDeserialization-480x247.png 480w\" sizes=\"(max-width: 548px) 100vw, 548px\" \/><figcaption>Here&#8217;s our Statistic objects, freshly deserialized from JSON data.<\/figcaption><\/figure><\/div>\n\n\n\n<p>Fabulous. There you go. Simple polymorphic deserialization with the <code>System.Text.Json<\/code> namespace.<\/p>\n\n\n\n<p>Until next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Table of Contents New Technology and the Need to Deserialize JSONSaying Bye to the Third Party, Then Missing ThemThe Data [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[10],"tags":[41,42,45,44],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v14.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\r\n<title>Polymorphic Deserialization with JSON in .NET 5.0 - omni&#039;s hackpad<\/title>\r\n<meta name=\"description\" content=\"Demonstrates how I added support for polymorphic JSON deserialization with a reusable base converter class in order to read hacked game data.\" \/>\r\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\r\n<link rel=\"canonical\" href=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/\" \/>\r\n<meta property=\"og:locale\" content=\"en_US\" \/>\r\n<meta property=\"og:type\" content=\"article\" \/>\r\n<meta property=\"og:title\" content=\"Polymorphic Deserialization with JSON in .NET 5.0 - omni&#039;s hackpad\" \/>\r\n<meta property=\"og:description\" content=\"Demonstrates how I added support for polymorphic JSON deserialization with a reusable base converter class in order to read hacked game data.\" \/>\r\n<meta property=\"og:url\" content=\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/\" \/>\r\n<meta property=\"og:site_name\" content=\"omni&#039;s hackpad\" \/>\r\n<meta property=\"article:published_time\" content=\"2020-12-04T23:40:10+00:00\" \/>\r\n<meta property=\"article:modified_time\" content=\"2022-02-22T05:39:04+00:00\" \/>\r\n<meta property=\"og:image\" content=\"https:\/\/badecho.com\/wp-content\/uploads\/2020\/12\/StatisticsDeserialization.png\" \/>\r\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\r\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/badecho.com\/#website\",\"url\":\"https:\/\/badecho.com\/\",\"name\":\"omni&#039;s hackpad\",\"description\":\"Game Code Disassembly. Omnified Modification. Madness.\",\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/badecho.com\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/badecho.com\/wp-content\/uploads\/2020\/12\/StatisticsDeserialization.png\",\"width\":548,\"height\":282,\"caption\":\"Here's our Statistic objects, freshly deserialized from JSON data.\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#webpage\",\"url\":\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/\",\"name\":\"Polymorphic Deserialization with JSON in .NET 5.0 - omni&#039;s hackpad\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#primaryimage\"},\"datePublished\":\"2020-12-04T23:40:10+00:00\",\"dateModified\":\"2022-02-22T05:39:04+00:00\",\"description\":\"Demonstrates how I added support for polymorphic JSON deserialization with a reusable base converter class in order to read hacked game data.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/\"]}]},{\"@type\":\"Article\",\"@id\":\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#webpage\"},\"author\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"headline\":\"Polymorphic Deserialization With System.Text.Json in .NET 5.0\",\"datePublished\":\"2020-12-04T23:40:10+00:00\",\"dateModified\":\"2022-02-22T05:39:04+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#webpage\"},\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"image\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2020\/12\/04\/polymorphic-json-deserialization\/#primaryimage\"},\"keywords\":\".NET,C#,JSON,Statsplash\",\"articleSection\":\"General Dev\",\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\",\"name\":\"Matt Weber\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/7e345ac2708b3a41c7bd70a4a0440d41?s=96&d=mm&r=g\",\"caption\":\"Matt Weber\"},\"logo\":{\"@id\":\"https:\/\/badecho.com\/#personlogo\"}}]}<\/script>\r\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/726"}],"collection":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/comments?post=726"}],"version-history":[{"count":29,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/726\/revisions"}],"predecessor-version":[{"id":2345,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/726\/revisions\/2345"}],"wp:attachment":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/media?parent=726"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/categories?post=726"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/tags?post=726"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}