Serialize and deserialize a multidimensional array to JSON in C#

By FoxLearn 12/25/2024 4:46:11 AM   40
By default, System.Text.Json does not support serializing or deserializing multidimensional arrays.

When attempting to serialize or deserialize a two-dimensional array, for example, it throws a System.NotSupportedException with the message: The type 'System.Int[,] is not supported. Fortunately, there are ways to work around this limitation.

In this article, we will explore three potential solutions to handle multidimensional arrays with System.Text.Json:

  1. Use Newtonsoft.Json, which supports multidimensional arrays.
  2. Use a jagged array (e.g., int[][]), which System.Text.Json can handle.
  3. Write a custom JsonConverter to handle multidimensional arrays manually

1. Create a Custom JsonConverter Class

The first step is to create a class that implements JsonConverter<int[,]>, which can handle the serialization and deserialization of two-dimensional integer arrays.

using System.Text.Json.Serialization;
using System.Text.Json;

public class TwoDimensionalIntArrayJsonConverter : JsonConverter<int[,]>
{
    public override int[,]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, int[,] value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

2. Serialize a Multidimensional Array in the Write() Method

To serialize the multidimensional array in the Write() method, we need to convert the two-dimensional array into a JSON array of arrays. Here's the implementation for serializing a two-dimensional array of integers:

  1. Start the array with writer.WriteStartArray().
  2. Use writer.WriteNumberValue() to write each integer value to the array.
  3. Close the array with writer.WriteEndArray().
public override void Write(Utf8JsonWriter writer, int[,] value, JsonSerializerOptions options)
{
    writer.WriteStartArray();  // Start the outer array
    for (int i = 0; i < value.GetLength(0); i++)
    {
        writer.WriteStartArray();  // Start the inner array (row)
        for (int j = 0; j < value.GetLength(1); j++)
        {
            writer.WriteNumberValue(value[i, j]);  // Write each number
        }
        writer.WriteEndArray();  // End the inner array (row)
    }
    writer.WriteEndArray();  // End the outer array
}

3. Deserialize to a Multidimensional Array in the Read() Method

When we serialize the two-dimensional array, it is written as a JSON array of arrays, like this:

[[1, 2], [3, 4]]

To deserialize this JSON back into a two-dimensional array, we will perform two main steps:

  1. Initialize the two-dimensional array: We first need to determine the dimensions of the array. Since Utf8JsonReader is forward-only, we use JsonDocument to parse the JSON and find out the sizes of the outer and inner arrays.
  2. Loop through the nested arrays: We will iterate over the JSON array, reading the values and placing them into the two-dimensional array.
public override int[,]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    using var jsonDoc = JsonDocument.ParseValue(ref reader);
    
    // Get the length of the outer array (rows) and the inner array (columns)
    var rowLength = jsonDoc.RootElement.GetArrayLength();
    var columnLength = jsonDoc.RootElement.EnumerateArray().First().GetArrayLength();

    int[,] grid = new int[rowLength, columnLength];

    int row = 0;
    foreach (var array in jsonDoc.RootElement.EnumerateArray())
    {
        int column = 0;
        foreach (var number in array.EnumerateArray())
        {
            grid[row, column] = number.GetInt32();  // Assign values to the grid
            column++;
        }
        row++;
    }

    return grid;
}

In this implementation:

  • We first parse the JSON with JsonDocument.ParseValue() to get the size of the array.
  • We then iterate through the arrays, populating the two-dimensional int[,] array.

4. Use the Custom JsonConverter

Once the JsonConverter is implemented, it can be used with JsonSerializerOptions. To apply the converter, you need to add it to the Converters collection of JsonSerializerOptions:

using System.Text.Json;

int[,] grid = new int[,] { { 5, 10, 15 }, { 20, 25, 30 }, { 35, 40, 45 } };

var options = new JsonSerializerOptions();
options.Converters.Add(new TwoDimensionalIntArrayJsonConverter());

var json = JsonSerializer.Serialize(grid, options);

Console.WriteLine($"Serialized: {json}");

var gridFromJson = JsonSerializer.Deserialize<int[,]>(json, options);

Console.WriteLine("Deserialized:");
Console.WriteLine($"[0,0] = {gridFromJson[0,0]}");
Console.WriteLine($"[0,1] = {gridFromJson[0,1]}");
Console.WriteLine($"[0,2] = {gridFromJson[0,2]}");
Console.WriteLine($"[1,0] = {gridFromJson[1,0]}");
Console.WriteLine($"[1,1] = {gridFromJson[1,1]}");
Console.WriteLine($"[1,2] = {gridFromJson[1,2]}");
Console.WriteLine($"[2,0] = {gridFromJson[2,0]}");
Console.WriteLine($"[2,1] = {gridFromJson[2,1]}");
Console.WriteLine($"[2,2] = {gridFromJson[2,2]}");

The output will show the serialized JSON string and the deserialized values of the 3x3 matrix:

Serialized: [[5,10,15],[20,25,30],[35,40,45]]
Deserialized:
[0,0] = 5
[0,1] = 10
[0,2] = 15
[1,0] = 20
[1,1] = 25
[1,2] = 30
[2,0] = 35
[2,1] = 40
[2,2] = 45

In this example, we are serializing and deserializing a 3x3 integer array with the custom JsonConverter we created earlier. The JSON string that represents this array is [[5,10,15],[20,25,30],[35,40,45]], and upon deserialization, we are able to retrieve each value from the respective positions in the array.

If you prefer to avoid custom converters, other solutions like using Newtonsoft.Json or jagged arrays (e.g., int[][]) are also valid options. However, writing a custom JsonConverter provides a flexible and efficient way to handle this scenario.