From C# objects to CSV With System.Text.Json
Photo by israel palacio on Unsplash

From C# objects to CSV With System.Text.Json

in

Introduction

In this post, I will discuss a method for converting a list of C# objects to a CSV file. This approach can be used to implement export to CSV functionality.

Serialization

Converting a C# object into another format is called serialization. The .NET base class library includes the System.Text.Json namespace. It provides classes that make it easy to work with JSON files, including serialization and deserialization. Deserialization is the opposite of serialization, which converts a JSON object into a .NET object. In this article, we focus on serialization.

Demo Data

Our demo project will create a CSV of the top-paying technology jobs based on this article on indeed.com: The Top 25 Highest-Paying Technology Jobs and Their Duties. We will only model our data for the top three highest-paying tech jobs.

Here is our Job class:

public class Job
{
    public string Title { get; set; } = string.Empty;
    public decimal Salary { get; set; }
    public string Description { get; set; } = string.Empty;
}

Next we will create a list of jobs.

var topThreeJobs = new List<Job>()
{
    new()
    {
        Title = "Data Scientist",
        Description = "A data scientist is responsible for collecting, cleaning and munging data to meet a company's purpose.",
        Salary = 140722
    },
    new()
    {
        Title = "Enterprise Architect",
        Description = "Enterprise architects are responsible for integrating an organization's information applications and programs.",
        Salary = 137980
    },
    new()
    {
        Title = "Software Architect",
        Description = "Software architects are software development professionals who oversee the technology infrastructure of a company.",
        Salary = 133358
    }
};

Serialize to JSON

Now that we have our demo data, it’s time to serialize our topThreeJobs object. We can use the built-in System.Text.Json JsonSerializer class to accomplish this task.

using System.Text.Json;

var options = new JsonSerializerOptions(){WriteIndented = true}
var serializedTopThreeJobs = JsonSerializer.Serialize(topThreeJobs, options);

I passed the optional options parameter to make the printed JSON more readable. If we write the serializedTopThreeJobs variable to the console, you will see that we have JSON!

Console.WriteLine(serializedTopThreeJobs)
[
  {
    "Title": "Data Scientist",
    "Salary": 140722,
    "Description": "A data scientist is responsible for collecting, cleaning and munging data to meet a company\u0027s purpose."
  },
  {
    "Title": "Enterprise Architect",
    "Salary": 137980,
    "Description": "Enterprise architects are responsible for integrating an organization\u0027s information applications and programs."
  },
  {
    "Title": "Software Architect",
    "Salary": 133358,
    "Description": "Software architects are software development professionals who oversee the technology infrastructure of a company."
  }
]

To CSV

Now, we need to convert our new JSON data to a CSV. Since we have a JSON array, we must find a way to iterate the JSON array and write the list of property values as a row in our CSV file. We can use JsonSerializer for this too. However, Instead of calling the Serialize method on the JsonSerializer class, let’s call the SerializeToDocument, which will convert our C# object to a JsonDocument, which will allow us to examine the JSON programmatically. Additionally, SerializerToDocument implements IDisposable, so we assign our variable with using a declaration.

using var document = JsonSerializer.SerializeToDocument(topThreeJobs);

A JsonDocument is composed of JsonElements and each JsonDocument will have a RootElement.

Let’s assign the root element to a variable;

JsonElement[] root = document.RootElement.EnumerateArray();

Next, we initialize a new instance of StringBuilder to build our CSV string;

var builder = new StringBuilder();

We need the properties of each Job so that we can write those properties as a row in the CSV file. But before we do that, we need to get the column headers. For this, we will iterate through the first object’s properties and add the the JsonProperty.Name value to our builder object.

if (root.Any())
{
    var headers = root.First().EnumerateObject().Select(o => o.Name);
    builder.AppendJoin(',', headers);
}

Next, we can print builder object to the console to check our headers.

Console.WriteLine(builder);
Title,Salary,Description

Alright, that looks good! Now we can add the rows to our builder object.

if (root.Any())
{
    var headers = root.First().EnumerateObject().Select(o => o.Name);
    builder.AppendJoin(',', headers);

    // new
    foreach (var element in root)
    {
        var row = element.EnumerateObject().Select(o => o.Value.ToString());
        builder.AppendJoin(',', row);
        builder.AppendLine();
    }
}

We used the EnumerateObject method to iterate through the JsonProperty object and add JsonProperty.Value our builder object. Finally, we write the builder string to a file.

File.WriteAllText("jobs.csv", builder.ToString(), new UTF8Encoding());

After opening jobs.csv, you will notice that we have a formatting issues.

A CSV file with a title, salary and description column

Some of the description text in the first row was added to a new column. The program I loaded the CSV file into reads the comma in any text as the start of a new column. We could write a utility method to escape the commas before appending a new value to the builder object. A better way is to use the tools that Microsoft.Text.Json provides to handle and implement any custom logic to convert JSON values.

I will create a custom converter to handle escaping the commas.

public class JsonConverterEscapeCommas : JsonConverter<string?>
{
    public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return reader.GetString();
    }

    public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
    {
        if (value is not null && value.Contains(','))
        {
            writer.WriteStringValue($"\"{value}\"");
        }
        else
        {
            writer.WriteStringValue(value);
        }
    }
}

In the preceding code, I created a custom converted called JsonConverterEscapeCommas which is derived from JsonConverter<string?> and overrides the Read and Write methods. We only need to modify the Write method since this post is focused on serialization.

In the Write method body, we do a check to see if the value is not null and contains a comma. If it does, we surround the value with double quotes to adhere to the RFC 480 Standard.

Next, we have to implement our custom converter. There are a couple ways to do this. We can add the custom converter to the JsonSerializerOptions like so:

var options = new JsonSerializerOptions()
{
   //...
   Converters =
   {
       new JsonConverterEscapeCommas()
   }
};

However, this would not be ideal for our use case because every string value would be passed through the custom converter. We only want to apply this convert to the Description property of the Job class. We can do this by simply decorating the property on the model with the JsonConverterAttribute.

public class Job
{
    public string Title { get; set; } = string.Empty;
    public decimal Salary { get; set; }

    [JsonConverter(typeof(JsonConverterEscapeCommas))]
    public string Description { get; set; } = string.Empty;
}

Let’s test out the custom converter to see if the formatting issues in the CSV are resolved. After running the application, a new CSV file is created, and it looks great! 🎉

A CSV file with a title, salary and description column

Conclusion

The System.Text.Json namespace, included in the .NET base class library, is very useful, and this blog post barely scratches the surface of its capabilities. The demo provided in this post highlights how you can easily serialize objects and even control how the JsonSerializer writes values, opening up many possibilities when customizing how values are converted to JSON.