Change JsonConvert to JsonSerializer

When using a web API, a program has to implement serialize and deserialize features in many cases.
the ‘Newtonsoft.Json.JsonConvert.Deserialize(_json_)’ and the ‘Newtonsoft.Json.JsonConvert.SerializeObject(_object_)’ is useful so far, and if a program unknowns a json text as type, the ‘Newtonsoft.Json.JsonConvert.Deserialize(_json_)’ and the ‘Newtonsoft.Json.Linq.JArray[“_propertyname_”]’ is useful.
Microsoft provides new docs ‘How to migrate from Newtonsoft.Json to System.Text.Json‘.
Here this article describes how to deserialize JSON text with a type definition using System.Text.Json.JsonSerializer, and also how to deserialize unknown type JSON text. The ‘System.Text.Json.JsonSerializer.Serialize(_object_)’ and the ‘System.Text.Json.JsonDocument.Parse(_json_).RootElement.GetProperty(“_propertyname_”).GetString()’ are mentioned too.

>>The code sample of this article

・Create WinUI App

The code sample of this article is created as the Windows UI 3 application(Hereinafter referred to as WinUI). The WinUI 3 app is described at the Microsoft Docs article ‘Windows UI Library 3.0 Preview 1 (May 2020)‘ as setting the environment to create Windows UI app. And also the reference to the ‘Xaml Controls Gallery (WinUI 3.0 Preview 1 branch)‘ is mentioned.

I have decided to change the ‘NewtonSoft’ namespace provides serialization features (that I had used a long time), to System namespace at this opportunity.

The WinUI app is created from a template at first.

The below figure is the structure of the solution(left). And this article describes compared with the ‘Newtonsoft’ namespace and the ‘System’ namespace, thus the code sample defined a reference to the library through the NuGet(rihgt).

・Create View

At first, it is good to define simple title of the window and put one or two buttons at the view then run the application press F5 key, because the WinUI 3 app is still a preview that has not a design feature for Visual Studio. The figures below define the title of the window.

If it is able to launch, continue to design complex layouts at the next.

The text box above will hold something JSON string, and the text box below will generate formatted text of the object that analyzed from JSON string above.

This sample is omitted visual design, actually, the WinUI 3 can implement the Fluent Design using the Fluent Design system of Windows 10. For instance, can apply shadow to the controls, can use modern controls such as the hiding scroll bars(thin bar), and can use the dark mode theme(see Microsoft Build 2020 session ‘Everything you need to know about WinUI 11:43 -‘).

In the code behind, when the button pressed, the app will acquire the JSON string from web API through the facade. This JSON string will be put into the public property of the facade as the ‘original JSON string’. then in this UI thread, put it to the text property of the text box that means the ‘original JSON string’. As the same, the web API URL also set to the public property of the facade at now, in the future it might be able to set from out of the facade.
The button sends the argument that means which namespaces will be used to deserialize the JSON string. Then acquire a formatted string as a result that will be put to the text property of the text box that means the ‘Formatted string’.

On the original JSON string as a sample will acquire from the web API that is provided from an adaptive card template list. It is a good sample because it provides not a standard typed JSON string.

This JSON string contains all templates of the adaptive card, so the first layer elements of the root element have not property name. This kind type of JSON string is impossible to deserialize through prepared types. So this web API provides search features of organization information. Please refer to the Microsoft document ‘Adaptive Cards Template Service‘.

{
  "cdm": {
    "templates": [
      {
        "file": "Categories.json",
        "fullPath": "cdm/Categories.json"
      }
    ]
  },
  "finos.org": {
    "templates": [
      {
        "file": "Bond.json",
        "fullPath": "finos.org/Bond.json"
      }
    ]
  },
  "github.com": {
    "templates": [
      {
        "file": "issue_webhook.data.json",
        "fullPath": "github.com/issue_webhook.data.json"
      },
      {
        "file": "issue_webhook.json",
        "fullPath": "github.com/issue_webhook.json"
      }
    ]
  },
  ...
}

・Flow per conditions

In the facade, if the argument means using the ‘Newtonsoft’ namespace, the method deserializes the JSON string using correct namespace(in the left figure). In the case of the ‘System’ namespace, the method throws the part that picked github.com of the JSON string to the method that parses the JSON(in the right figure). Not use the ‘Newtonsoft’ namespace from now, thus it divided from the method using the ‘System’ namespace.

This type of JSON string can not deserialize through prepared type, because the property that wants to except is only one(organization name such as the ‘github.com’ in above JSON string figure) and it can not use the ‘JsonExtensionData’ attribute of the ‘System.Text.Json.Serialization.JsonExtensionDataAttribute’ class(see Microsoft document ‘How to serialize and deserialize (marshal and unmarshal) JSON in .NET‘).

In Case 3, It does not use the ‘CardModel’ type but use the ‘templates’ property of the ‘RootElement’ that parse by the ‘JsonDocument’, because as aforementioned, a prepared type can not define using the ‘JsonExtensionData’ attribute.

・Convert from the Newtonsoft namespace to the System namespace

The comment ‘Case 1’ of the ‘GetCard’ method is converted to the comment ‘Case 3’ of the ‘GetJsonString’ method at the ‘TextController’ class. These processes are sample code of using prepared type to deserialize an object.

// Case 1. Using the Newtonsoft namespace to parse an object as deserializing through prepared type. 
var cardTemplates = JsonConvert.DeserializeObject<CardModel>(cards);
results.Add("Result of the Case 1.");
foreach (var t in cardTemplates.templates)
{
    results.Add(string.Format("file = [{0}], fullPath = [{1}]", t.file, t.fullPath));
}
result = string.Join(Environment.NewLine, results);

to

// Case 3. Using the System namespace to parse an object as deserializing through prepared type. 
var cards = JsonDocument.Parse(json).RootElement.GetProperty("templates");
var templates = JsonSerializer.Deserialize<List<Template>>(cards.ToString());
result.Add("Result of the Case 3.");
foreach (var t in templates)
{
    result.Add(string.Format("file = [{0}], fullPath = [{1}]", t.file, t.fullPath));
}

The type ‘CardModel’ has the ‘templates’ property as a list of the ‘Template’ type. The ‘Template’ type has the ‘file’ property and the ‘fullPath’ property as string type. The name of these properties can define the camel-type because these properties have the ‘JsonPropertyName’ attribute. These attributes have to coincident with the name of the property name of the JSON string.

In Case 3, It does not use the ‘CardModel’ type but use the ‘templates’ property of the ‘RootElement’ that parse by the ‘JsonDocument’, because as aforementioned, a prepared type can not define using the ‘JsonExtensionData’ attribute.

On the other hand, instead of using a prepared type, using the ‘JsonElement’ that created by the ‘EnumerateArray’ method of the parsed object.

// Case 2. To parse an object as deserializing that through the 'JObect' of the Newtonsoft namespace.
// Use this process if you can't prepare the type of the JSON, or the type is not a complex structure.
var templates = JsonConvert.DeserializeObject<JObject>(cards);
var templatesArray = templates["templates"].ToArray();
results.Add("Result of the Case 2.");
foreach (var t in templatesArray)
{
    results.Add(string.Format("file = [{0}], fullPath = [{1}]", t["file"].ToString(), t["fullPath"].ToString()));
}
result = string.Join(Environment.NewLine, results);

to

// Case 4. To parse an object as deserializing that through the 'JsonDocument' of the System namespace.
// Use this process if you can't prepare the type of the JSON, or the type is not a complex structure.
result.Add("Result of the Case 4.");
foreach (var t in cards.EnumerateArray().ToList())
{
    result.Add(string.Format("file = [{0}], fullPath = [{1}]", t.GetProperty("file").GetString(), t.GetProperty("fullPath").GetString()));
}
return string.Join(Environment.NewLine,result);

About takao

I'm Microsoft MVP since June 2010.