TopoJSON is a compact encoding of geographic data, and it is common for each feature in a TopoJSON file to carry an attribute object rather than a flat set of key-value pairs. Exporting that structure to CSV is where things usually go wrong: most tools either drop the nested object or dump it back out as a JSON string wedged into a single cell. This guide shows how to convert TopoJSON to CSV in C# using Aspose.GIS, keeping every attribute addressable as its own column and optionally writing the geometry alongside it as Well-Known Text.
The payoff is that a hierarchical GIS dataset becomes a flat table that SQL Server, Power BI, pandas, or a plain spreadsheet can consume directly, with no bespoke JSON parsing layer in your application.
Why Convert TopoJSON to CSV using Aspose.GIS
Consider a TopoJSON feature whose properties look like this:
{
"name": "North District",
"prop1": { "this": "that" }
}
A naive CSV writer has three bad options: drop prop1, serialise it as {"this":"that"} into one cell, or fail. All three push the problem downstream, where someone has to re-parse JSON out of a text column before they can filter or join on it.
Aspose.GIS takes a different approach. When the TopoJSON driver reads the file, it projects the nested object into the layer schema as a flat attribute named after its path, joining parent and child with an underscore. The feature above is exposed as two ordinary columns:
| Column | Value |
|---|---|
name | North District |
prop1_this | that |
Because the flattening happens at read time, the conversion step needs no special handling at all. Whatever the layer exposes is what gets written, so prop1_this lands in the CSV as a first-class column you can sort, filter, and join on.
Prerequisites of TopoJSON to CSV Conversion
- .NET 6.0 or later, or .NET Framework 4.6.2 or later
- Aspose.GIS for .NET, installed from NuGet
- A TopoJSON file containing at least one nested attribute object
Install the package with the .NET CLI:
dotnet add package Aspose.GIS
Or from the Package Manager Console:
Install-Package Aspose.GIS
One naming detail catches people out: the NuGet package and assembly are called Aspose.GIS, but the root namespace is Aspose.Gis with a lowercase is. The CSV driver options live in Aspose.Gis.Formats.Csv.
using Aspose.Gis;
using Aspose.Gis.Formats.Csv;
Product details are on the Aspose.GIS for .NET product page, with the full type listing in the API reference.
Inspect the Flattened Schema Before You Convert
What you will accomplish: Open the TopoJSON layer and print its attribute names so you know exactly which columns the CSV will contain.
This step is worth doing once against a representative file. It tells you the real column names produced by flattening, which is far more reliable than guessing at them from the raw JSON.
using System;
using Aspose.Gis;
using (VectorLayer layer = VectorLayer.Open("data/regions.topojson", Drivers.TopoJson))
{
Console.WriteLine($"Features: {layer.Count}");
foreach (FeatureAttribute attribute in layer.Attributes)
{
Console.WriteLine($" {attribute.Name} ({attribute.DataType})");
}
}
For the sample feature shown earlier, this prints name (String) and prop1_this (String). The nested object never appears as an attribute in its own right, which is exactly what you want.
Convert TopoJSON to CSV While Preserving Nested Attributes
What you will accomplish: Load a TopoJSON file, write every flattened attribute to a CSV file, and read the result back to confirm the column made it through.
Step-by-step process
- Define the source TopoJSON path and the destination CSV path.
- Open the source with
VectorLayer.Openand confirm the flattened attribute exists. - Call the static
VectorLayer.Convertoverload with the source and destination drivers. - Re-open the CSV with
Drivers.Csvand check the column count and values.
The following example converts a TopoJSON file to CSV, flattening nested properties along the way.
using System;
using System.IO;
using Aspose.Gis;
class TopoJsonToCsv
{
static void Main()
{
string sourcePath = Path.Combine("data", "regions.topojson");
string destinationPath = Path.Combine("output", "regions.csv");
Directory.CreateDirectory("output");
// Confirm the nested property was flattened when the layer was opened.
using (VectorLayer source = VectorLayer.Open(sourcePath, Drivers.TopoJson))
{
Console.WriteLine($"Source features: {source.Count}");
Console.WriteLine($"Has prop1_this: {source.Attributes.Contains("prop1_this")}");
}
// No options are needed. The conversion writes whatever columns the
// source layer exposes, flattened attributes included.
VectorLayer.Convert(sourcePath, Drivers.TopoJson, destinationPath, Drivers.Csv);
// Read the CSV back and verify the schema survived the round trip.
using (VectorLayer csv = VectorLayer.Open(destinationPath, Drivers.Csv))
{
Console.WriteLine($"Rows: {csv.Count}, Columns: {csv.Attributes.Count}");
if (csv.Attributes.Contains("prop1_this"))
{
Console.WriteLine($"First value: {csv[0].GetValue<string>("prop1_this")}");
}
// No WKT column was requested, so features read back without geometry.
Console.WriteLine($"Geometry empty: {csv[0].Geometry.IsEmpty}");
}
}
}
Explanation
VectorLayer.Open(string, FileDriver)opens the file read-only.Drivers.TopoJsonreturns the driver for the TopoJSON format.FeatureAttributeCollection.Contains(string)is the cheapest way to test for a column by name.IndexOfgives you its position if you need it.VectorLayer.Convert(string, FileDriver, string, FileDriver)is a static helper that streams the source into the destination format in one call. There is also an overload takingConversionOptions, used in the next section.Feature.GetValue<T>(string)reads a typed attribute value. UseGetValueOrDefault<T>(string)when a column may be missing or null, sinceGetValue<T>assumes the value is present.- The final check is the one that surprises people. A CSV file has no geometry column unless you create one, so every feature comes back with an empty geometry. That is the default behaviour, not a conversion failure, and the next section fixes it.
Wrapping both layers in using blocks matters here: VectorLayer implements IDisposable, and the destination file is not guaranteed to be flushed and closed until it is disposed.
Include Geometry as WKT Using CsvOptions
What you will accomplish: Produce a CSV that carries both the flattened attributes and a column holding each feature’s geometry as Well-Known Text.
Step-by-step process
- Create a
CsvOptionsinstance and setColumnWktto your chosen column name. - Assign it to
ConversionOptions.DestinationDriverOptions. - Call the
VectorLayer.Convertoverload that accepts conversion options. - Re-open the CSV with the same
CsvOptionsso the driver knows which column to parse as geometry.
using System;
using System.IO;
using Aspose.Gis;
using Aspose.Gis.Formats.Csv;
class TopoJsonToCsvWithGeometry
{
static void Main()
{
string sourcePath = Path.Combine("data", "regions.topojson");
string destinationPath = Path.Combine("output", "regions-wkt.csv");
Directory.CreateDirectory("output");
// Ask the CSV driver for a geometry column named "GeometryWkt".
var csvOptions = new CsvOptions
{
ColumnWkt = "GeometryWkt"
};
var conversionOptions = new ConversionOptions
{
DestinationDriverOptions = csvOptions
};
VectorLayer.Convert(
sourcePath,
Drivers.TopoJson,
destinationPath,
Drivers.Csv,
conversionOptions);
// Pass the same options when reading, or the WKT column is just text.
using (VectorLayer layer = VectorLayer.Open(destinationPath, Drivers.Csv, csvOptions))
{
foreach (Feature feature in layer)
{
string name = feature.GetValueOrDefault<string>("name");
string wkt = feature.Geometry.IsEmpty ? "(empty)" : feature.Geometry.AsText();
Console.WriteLine($"{name}: {wkt}");
}
}
}
}
Explanation
CsvOptions.ColumnWktnames the column that holds each feature’s geometry in WKT form. It defaults tonull, and leaving it null or empty is precisely why the earlier example produced no geometry.ConversionOptions.DestinationDriverOptionsis typed asDriverOptions, the base class for every driver’s settings object. Passing aCsvOptionswhere the destination driver isDrivers.Csvis what makes the setting take effect; passing options of the wrong type for a driver raises anArgumentException.- The same
CsvOptionsinstance is reused on the read side. Without it, the CSV driver has no reason to treatGeometryWktas anything other than another string column. IGeometry.AsText()returns the ISO WKT representation, so the printed output matches what was written to the file.
The symmetry is the point: the attribute columns behave identically whether or not geometry is included, so you can turn the WKT column on and off without touching the rest of your pipeline.
Reproject During Conversion
If the consuming system expects a specific coordinate system, ask for it as part of the conversion rather than post-processing the CSV. ConversionOptions carries a spatial reference alongside the driver options:
var conversionOptions = new ConversionOptions
{
DestinationDriverOptions = new CsvOptions { ColumnWkt = "GeometryWkt" },
DestinationSpatialReferenceSystem = SpatialReferenceSystem.Wgs84
};
VectorLayer.Convert(sourcePath, Drivers.TopoJson, destinationPath, Drivers.Csv, conversionOptions);
Not every destination driver supports every spatial reference system. Call Drivers.Csv.SupportsSpatialReferenceSystem(...) first if the target system is configurable at runtime, and fall back to a null options object when it is not supported.
Batch Convert a Folder of TopoJSON Files
The API is identical for one file and for a thousand. The only rule worth remembering when you parallelise is that each worker needs its own layer and options objects.
using System;
using System.IO;
using System.Threading.Tasks;
using Aspose.Gis;
using Aspose.Gis.Formats.Csv;
static void ConvertFolder(string inputFolder, string outputFolder)
{
Directory.CreateDirectory(outputFolder);
var files = Directory.GetFiles(inputFolder, "*.topojson");
Parallel.ForEach(files, file =>
{
string destination = Path.Combine(
outputFolder,
Path.GetFileNameWithoutExtension(file) + ".csv");
// Fresh options per iteration: nothing is shared across threads.
var options = new ConversionOptions
{
DestinationDriverOptions = new CsvOptions { ColumnWkt = "GeometryWkt" }
};
try
{
VectorLayer.Convert(file, Drivers.TopoJson, destination, Drivers.Csv, options);
}
catch (GisException ex)
{
Console.Error.WriteLine($"Skipped {Path.GetFileName(file)}: {ex.Message}");
}
});
}
Catching GisException per file rather than around the whole loop means one malformed TopoJSON file logs a warning instead of aborting the batch.
Get a Free License
Aspose.GIS runs in evaluation mode until a license is applied. Request a free temporary license from the Aspose temporary license page and apply it once at application startup, before any other Aspose.GIS call:
var license = new Aspose.Gis.License();
license.SetLicense("Aspose.GIS.NET.lic");
Free Additional Resources
Conclusion
Converting TopoJSON with nested properties to CSV in .NET comes down to one useful piece of behaviour: Aspose.GIS flattens nested attribute objects into named columns such as prop1_this while the layer is being read, so VectorLayer.Convert needs no custom logic to preserve them. Add CsvOptions.ColumnWkt when the downstream system needs geometry, add DestinationSpatialReferenceSystem when it needs a specific projection, and loop the same call when you need to process a folder. The result is a flat, analysis-ready table produced by a handful of lines of C#, with no JSON parsing layer of your own to maintain.
FAQs
Can Aspose.GIS flatten nested JSON objects when converting to CSV? Yes. The TopoJSON driver exposes a nested attribute object as separate columns when the layer is opened, joining parent and child names with an underscore, so the flattening is already done before the conversion runs.
Do I need to write custom code to handle nested properties? No.
VectorLayer.Convertwrites whatever columns the source layer exposes, so nested properties need no extra parsing code of your own.How can I include geometry in the CSV output? Create a
CsvOptionsinstance, set itsColumnWktproperty to the column name you want, assign it toConversionOptions.DestinationDriverOptions, and pass that toVectorLayer.Convert.Why is the geometry empty when I read my CSV back? A CSV has no geometry column unless you ask for one. Set
ColumnWkton theCsvOptionsyou pass both when writing and when reading, otherwise the driver has no column from which to build a geometry.What exceptions should I handle during conversion? The documented ones are
ArgumentNullExceptionfor a null path,ArgumentExceptionwhen an options object does not match the driver,GisExceptionwhen a feature cannot be read from the file, andIOExceptionfor underlying I/O failures.Can I convert a batch of TopoJSON files at once? Yes. Loop over the file list and call
VectorLayer.Convertfor each file. If you parallelise the loop, give every worker its ownVectorLayerand options instances rather than sharing one across threads.Does the conversion change the coordinate system? Not by default. To reproject during conversion, set
ConversionOptions.DestinationSpatialReferenceSystemto the target system before callingConvert.
