Working with JsonObject, JsonNode, JsonValue and JsonArray [System.Text.Json API]
Tags: dotnet, csharp, programming
Published: 2024-05-20 08:58:19

Most of the articles which I found on the web about the `System.Text.Json` API dealt with serialization and de-serialization with little being explored about creating a JSON entity on the fly. 
A recent use-case required us to build a data entity dynamically while running through a set of business logic (emphasis on the fact that the output here could not be modelled as a POCO since it was going to take form during the run-time). This entity would be serialized and sent over to a downstream service. While one could opt for the[ Dynamic Objects](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/interop/walkthrough-creating-and-using-dynamic-objects), we decided to work our way with JSON objects.


When working with dynamic data through the `System.Text.Json` API, everything revolves around the [JsonNode](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.nodes.jsonnode?view=net-6.0) class. It is an abstract class and is derived by the [JsonArray](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.nodes.jsonarray?view=net-6.0), the [JsonObject ](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.nodes.jsonobject?view=net-6.0)and  the [JsonValue](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.nodes.jsonvalue?view=net-6.0). These can be thought of as the building blocks - we work with them more in the coming examples.

Before we actually dive into the `System.Text.Json` API, let's take a step back and re-visit the JSON standard. 

The [json.org](https://www.json.org/json-en.html) quotes the following

> JSON is built on two structures:
> A collection of name/value pairs. In various languages, this is realized as an object, record, struct, dictionary, hash table, keyed list, or associative array.
> An ordered list of values. In most languages, this is realized as an array, vector, list, or sequence.

It then elaborates how in JSON, this could take two forms - either an object and an array. 

- 
An object and an array in JSON can have a JSON value

- 
A value in JSON can also in turn be an array, a number a object , etc

We kind of get an idea that a json value, json object and the json array are all intertwined. 

Going back to System.Text.Json API, we have the `JsonObject ` which can be used to realise an object in JSON, `JsonArray `as an array and `JsonValue `for a value in JSON.

We start with `JsonValue ` and build our way up. The `JsonValue `is an abstract class (you cannot instantiate an abstract class). The next obvious thought is - how are we then going to realise a C# type here  without being able to instantiate the type ? Our C# counterpart could be anything string, double, int, etc.

The `JsonValue` class contains static methods (`Create`) for the C# value types and based on in-built implicit conversion, they are going to give you a type of `JsonNode` for the C# value you provided.

```csharp
var firstName = JsonValue.Create("Doofus");
var lastName = JsonValue.Create("Flintstones");
var addressPostcode = JsonValue.Create(1234);
var phoneNumber= JsonValue.Create(1234567890);
var knowsCSharp= JsonValue.Create(true);
```
You can always be explicit about the types too,

```csharp
var identifier = JsonValue.Create<Guid>(Guid.NewGuid());
var yearAtDev = JsonValue.Create<int>(5);
```
You might wonder what's the advantage here ? Also we said we want a  JsonValue but we are getting back a JsonNode - it will make sense in a while but for now what we can assert here is the type definition.

 As of .NET 8, through `System.Text.Json` API we also get [JsonValueKind](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonvaluekind?view=net-8.0) 

```csharp
public enum JsonValueKind : byte
{
 Undefined = 0,
 Object = 1,
 Array = 2,
 String = 3,
 Number = 4,
 True = 5,
 False = 6,
 Null = 7
}
```
While working with serialization and deserialization of data, the widely adopted standard is JSON. 

`JsonValue.Create<int>(5)`  would create a JSON value of kind number and 
`JsonValue.Create<string>("5")` would create a JSON value of kind string. This would matter when this data would be deserialized in any downstream system

```javascript
{
 "age" : 5,
 "age2" : "5"

}
```
would be deserialized differently, age would have to be deserialized as an integer and age2 as a string. Hence it is good to assert type definitions while building up the json data at the source and through the different factory method overloads (can also be achieved with the implicit conversions) , `System.Text.Json` API provides a cleaner way

We can also use the  `GetValueKind ` method to get the `JsonValueKind`

```csharp
   var guid = JsonValue.Create<Guid>(Guid.NewGuid());
   var jsonValueKind =  guid.GetValueKind();
```


Now that we have a fair understanding of the `JsonValue`, we try to use that to realise a `JsonArray`.

A `JsonArray` is but a collection of json values or json objects.

To be completely honest, I haven't explored much with `JsonArray  `except for a deserialization use-case. However I skimmed through the documentation and here is one sample to create a JSON array
`JsonArray  ` is a sealed class, you cannot inherit it but you can instantiate it.

```csharp
   var jsonArray = new JsonArray();
   jsonArray.Add(JsonValue.Create(23));
   jsonArray.Add(JsonValue.Create("23"));
```
The `Add ` method on the `JsonArray` accepts a parameter of type `JsonNode ` and if you recall the definition from the _json.org_ , a Json array could consist of a JSON object or  a JSON value - this blends quiet well here, that a `JsonNode `in `System.Text.Json` could be very well be substituted by JsonValue or `JsonObject ` i.e. you can add either of the types through the `Add `method on the `JsonArray` 

But please feel free to correct me here and add any relevant use case with `JsonArray `if you know of :)

Moving on to the `JsonObject `, 

`JsonObject` is basically a collection `KeyValuePair` of string and `JsonNode` ! It derives from the `JsonNode` and well holds a `JsonNode` too. 

In the beginning, the `JsonNode` had got me all confused but if you take a moment and appreciate the tree data structure and think of JSON as one, then a tree is just a collection of nodes. With JSON, the different entities are a value, object and an array so a node could be any one of these ? and may be then it makes sense that the `JsonValue`, `JsonArray` and `JsonObject` derive from the `JsonNode` . Anyway, I like to look at it that way.

With `JsonObject` then its just putting together the json values, arrays or even nested objects

```csharp
  var jsonObject = new System.Text.Json.Nodes.JsonObject()
  {

   ["firstName"] = JsonValue.Create("Doofus"),
   ["lastName "] = JsonValue.Create("Flintstones"),
   ["address"] = new System.Text.Json.Nodes.JsonObject
   {
    ["line1"]= "Stockholm",
    ["line2"]= "Sweden"
   },
   ["knowsCSharp"] = JsonValue.Create(true),
   ["yearsAtDev "] = JsonValue.Create<int>(5),
   ["hobbies"] = new JsonArray() { "music", "dance"}

  };

```

Also now might be a good time to look at how we could work with deserialization here

Usually when we get data over the wire, we get a JSON. In our consumer when we get a JSON response (say through a REST call) , we deserialize it in a POCO

```
{
  "FirstName": "Doofus",
  "LastName": "Flintstones",
  "Address": {
    "street": {
      "line1": "Stockholm",
      "line2": "Sweden",
      "line3": "Nordic"
    },
    "postcode": 1234
  }
}

```

the POCO could be modelled as

```csharp

class Person
{
      public string FirstName { get; set; } = string.Empty;
      public string LastName { get; set; } = string.Empty;
      public Address? Address { get; set; }


}

public class Address
{
      public Street? Street { get; set; }
      public int Postcode { get; set; }
}

public class Street
{
      public string Line1 { get; set; } = string.Empty;
      public string Line2 { get; set; } = string.Empty
      public string Line3 { get; set; } = string.Empty;
}

```

and we get the deserialized data by doing -

` var person = System.Text.Json.JsonSerializer.Deserialize<Person>(jsonResponseAsString);`

But what if we instead deserialize into a JsonObject and not a POCO ?

` var personAsJsonObject = System.Text.Json.JsonSerializer.Deserialize<JsonObject>(jsonResponseAsString);`

If you have used Newtonsoft before, you might be familiar with the JObject and we used an indexer to traverse it.

A `JsonObject ` if you recall is a key value pair, the value being of type `JsonNode`

If we want to access the property FirstName then we could either use the indexer

`personAsJsonObject["firstName"]` or `personAsJsonObject.TryGetPropertyValue("firstName", out var value)`

Both are going to give you a `JsonNode`, but a JsonNode could be either  a JsonValue,a JsonArray or JsonObject how would you know ?

On JsonNode, we can call AsValue(), AsArray() and AsObject() to get the corresponding type. Next, we could also assert the kind through GetValueKind and call any one of the factory methods we saw earlier to retrieve our value.

I did it very old-school way, something on the lines of this , you can have one each for JsonObject and JsonArray as well

```csharp
public static JsonValue ToJsonValue(JsonNode node)
{
    try
    {
        return node.AsValue();
    }
    catch (InvalidOperationException)
    {
      //handle it at the caller level by letting it fall-back to 
      next in chain , perhaps JsonArray ?
    }
}
```

Following is an extract from the .NET source code, and as you see an `InvalidOperationException `is going to get thrown if you try to call `AsValue()` -  say for example on a `JsonObject `( i.e. `JsonNode `which is of type `JsonObject`) . `AsValue()` would only yield a `JsonValue `on being called on a `JsonNode `which holds a `JsonValue`, for others we get an exception

```csharp
    public JsonValue AsValue()
    {
        JsonValue? jValue = this as JsonValue;

        if (jValue is null)
        {
            ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonValue));
        }

        return jValue;
    }
```

Next after you have retrieved a JsonValue, you can call the nice helper methods to retrieve a string, boolean, int, etc.

`jsonValue.TryGetValue(out string? value)`

While there is a lot of ground to cover here and also to learn, even for me ..I hope this serves as a starting point/ basics when you in future would want to explore this path

Any suggestions/improvements welcome.