Skip to content
TablePage.ai Open the app

Use JsonDocument to Prepare Nested JSON for a Public Table

Parse nested JSON with C# JsonDocument, validate fields, export a clean CSV, and check the table before publishing it publicly.

Share X in f
Wei Hu

An API response might contain an array of records, nested location objects and missing measurements. To publish it as a useful table, first decide what one row represents, then extract consistent columns.

In C#, System.Text.Json.JsonDocument is a read-only representation of parsed JSON. Its RootElement gives you access to objects, arrays and values through JsonElement. It is useful when you need to inspect a payload without defining a matching C# class; use JsonNode if you need to edit the JSON itself. Microsoft’s JSON DOM guide explains those choices.

This example turns nested JSON into a four-column CSV ready for publication review—not a universal JSON-to-CSV converter.

1. Define the row and column mapping

Save this fictional sample dataset as data.json:

{
  "measurement_date": "2026-10-01",
  "records": [
    {
      "id": "R01",
      "region": { "name": "North, coastal" },
      "rainfall_mm": 12.5
    },
    {
      "id": "R02",
      "region": { "name": "East" },
      "rainfall_mm": null
    },
    {
      "id": "R03",
      "region": { "name": "West" },
      "rainfall_mm": 0
    }
  ]
}

For this export, one row represents one record on the stated measurement date:

CSV column JSON source Export rule
id Each record’s id Required string
region Each record’s region.name Required string
measurement_date Root-level measurement_date Required string, repeated per row
rainfall_mm Each record’s rainfall_mm Number convertible to C# decimal; absent or null becomes blank

The date is carried into every row so it survives separation from the original response. The nested region object becomes one scalar column.

If your records contain arrays of readings, choose a different structure: one row per reading, or a separate readings table linked by record ID. Do not repeat a record-level total across child rows and then sum it as though each repetition were a new observation.

2. Parse, validate and write the CSV

Use the following as Program.cs in a .NET 8 or later console project. Place data.json in the program’s working directory, then run the project. A successful run writes—or overwrites—data.csv in that directory.

The code checks JSON types before conversion. In particular, TryGetDecimal does not parse a JSON string such as "12.5", and calling it on a non-number throws. It returns false if numeric conversion fails. Microsoft’s method reference documents this behavior.

This example holds the input and generated CSV in memory; it is not a streaming export for large datasets.

using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.Json;

using JsonDocument doc = JsonDocument.Parse(File.ReadAllText("data.json"));
JsonElement root = doc.RootElement;
string date = RequiredString(root, "measurement_date");

if (!root.TryGetProperty("records", out JsonElement records) ||
    records.ValueKind != JsonValueKind.Array)
    throw new InvalidDataException("records must be an array.");

var csv = new StringBuilder(
"id,region,measurement_date,rainfall_mm\r\n");

foreach (JsonElement row in records.EnumerateArray())
{
    string id = RequiredString(row, "id");
    if (!row.TryGetProperty("region", out JsonElement region) ||
        region.ValueKind != JsonValueKind.Object)
        throw new InvalidDataException("region must be an object.");

    string name = RequiredString(region, "name");
    string rainfall = "";

    if (row.TryGetProperty("rainfall_mm", out JsonElement value) &&
        value.ValueKind != JsonValueKind.Null)
    {
        if (value.ValueKind != JsonValueKind.Number ||
            !value.TryGetDecimal(out decimal amount))
            throw new InvalidDataException(
                "rainfall_mm must be a number convertible to decimal, or null.");

        rainfall = amount.ToString(CultureInfo.InvariantCulture);
    }

    csv.Append(CsvText(id)).Append(',')
       .Append(CsvText(name)).Append(',')
       .Append(CsvText(date)).Append(',')
.Append(rainfall).Append("\r\n");
}

// Write only after every row has passed validation.
File.WriteAllText("data.csv", csv.ToString(), new UTF8Encoding(false));

static string RequiredString(JsonElement obj, string property)
{
    if (obj.ValueKind != JsonValueKind.Object ||
        !obj.TryGetProperty(property, out JsonElement value) ||
        value.ValueKind != JsonValueKind.String)
        throw new InvalidDataException(property + " must be a string.");

    return value.GetString()!;
}

static string CsvText(string text) =>
"\"" + text.Replace("\"", "\"\"") + "\"";

The text helper surrounds fields with double quotes and doubles any internal quotes. That preserves commas, quotes and embedded line breaks as field content, following the escaping rules described in RFC 4180. Numeric output uses a decimal point regardless of the machine’s locale, using invariant-culture decimal formatting.

data.csv should contain:

id,region,measurement_date,rainfall_mm
"R01","North, coastal","2026-10-01",12.5
"R02","East","2026-10-01",
"R03","West","2026-10-01",0

Blank is not zero. This workflow deliberately collapses absent properties and explicit JSON nulls into the same blank cell. Add a status column if readers need to distinguish those cases.

For untrusted text, review formula-injection risk before opening the CSV in a spreadsheet. The escaping helper preserves CSV structure; it does not neutralize spreadsheet formulas. OWASP’s CSV injection guidance distinguishes that risk from ordinary quoting.

3. Check the conversion before publishing

Keep these checks separate from parsing:

  • Structure: Confirm three data rows and four columns. North, coastal must remain one field.
  • Meaning: Confirm the measurement date’s format and the unit definition. The code validates the date as a string, not as a calendar date.
  • Types: Verify rainfall sorts numerically and the null stays blank. Reject unexpected numeric strings rather than silently treating them as missing.
  • Scope: This export includes only the mapped fields. Keep the original JSON locally and record any deliberate omissions.

Property lookup is case-sensitive: rainfall_mm and Rainfall_mm are different names. Microsoft’s TryGetProperty reference confirms this.

Keep the document alive while reading its elements. The using declaration handles disposal here; if you return a JsonElement from a helper that disposes its owning document, return element.Clone() instead. Microsoft’s lifetime guidance explains why.

4. Publish only the reviewed table

As of October 24, 2026, TablePage’s homepage lists CSV, TSV, XLSX and XLS uploads that produce public dataset pages with filterable tables. For this workflow, upload the reviewed data.csv, not the original nested response.

Never upload sensitive information. Review both exported fields and individual values for private identifiers, credentials or confidential records.

Provide a dataset title, source URL, retrieval date, measurement units and missing-value definition alongside the shared table. Then inspect the published page: check the row count, the comma-containing region name, numeric sorting and blank rainfall value. If conversion losses matter to your readers, document them using the principles in reasons to convert data to CSV before sharing.