using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using CsvHelper.Configuration;
using CsvHelper.Configuration.Attributes;
using CsvHelper.Expressions;
using CsvHelper.TypeConversion;
public static partial class JsonExtensions
public static IEnumerable<IEnumerable<KeyValuePair<string, JValue>>> FlattenChildrenIntoRows(this JObject obj)
bool anyReturned = false;
var simpleChildren = obj.Properties().Where(p => p.Value is JValue).Select(p => new KeyValuePair<string, JValue>(p.Name, (JValue)p.Value)).ToList();
var complexChildren = obj.Properties().Where(p => !(p.Value is JValue)).Select(p => new KeyValuePair<string, JToken>(p.Name, p.Value));
foreach (var property in complexChildren)
foreach (var flattenedItems in FlattenChildrenIntoRows(property))
yield return simpleChildren.Concat(flattenedItems);
yield return simpleChildren;
static IEnumerable<IEnumerable<KeyValuePair<string, JValue>>> FlattenChildrenIntoRows(KeyValuePair<string, JToken> property)
if (property.Value is JValue value)
yield return new[] { new KeyValuePair<string, JValue>(property.Key, value) };
else if (property.Value is JArray array)
foreach (var item in array)
foreach (var flattenedItem in FlattenChildrenIntoRows(new KeyValuePair<string, JToken>(property.Key, item)))
yield return flattenedItem;
else if (property.Value is JObject obj)
foreach (var flattenedItems in FlattenChildrenIntoRows(obj))
yield return flattenedItems.Select(p => new KeyValuePair<string, JValue>(property.Key + "." + p.Key, p.Value));
throw new NotImplementedException();
public static partial class CsvExtensions
public static void WriteCsv(TextWriter writer, IReadOnlyList<IDictionary<string, string>> objects, CsvConfiguration config)
var headers = objects.SelectMany(o => o.Keys).Distinct();
using (var csv = new CsvWriter(writer, config))
foreach (var header in headers)
foreach (var obj in objects)
foreach (var header in headers)
if (obj.TryGetValue(header, out var value))
public static partial class CsvExtensions
public static void ConvertJsonToCsv(string inputPath, string outputPath)
using var reader = new StreamReader(inputPath);
using var writer = new StreamWriter(outputPath);
ConvertJsonToCsv(reader, writer);
public static void ConvertJsonToCsv(TextReader reader, TextWriter writer)
var settings = new JsonSerializerSettings
DateParseHandling = DateParseHandling.None,
using var jsonReader = new JsonTextReader(reader) { CloseInput = false };
var array = JsonSerializer.CreateDefault(settings).Deserialize<List<JObject>>(jsonReader);
.Select(o => o.FlattenChildrenIntoRows())
.SelectMany(pairs => pairs)
.Select(pairs => pairs.ToDictionary(p => p.Key, p => p.Value.Value == null ? null : (string)Convert.ChangeType(p.Value.Value, typeof(string), CultureInfo.InvariantCulture)))
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
CsvExtensions.WriteCsv(writer, list, config);
public static void Test()
var jsonString = GetJson();
using var reader = new StringReader(response.Content);
using var writer = new StringWriter();
CsvExtensions.ConvertJsonToCsv(reader, writer);
var csvText = writer.ToString();
Console.WriteLine(csvText);
static string GetJson() { return @"[
""transaction_id"": ""00000352"",
""transaction_type"": ""New"",
""transaction_date"": ""2018-08-23T00:00:00"",
""sold_to_id"": ""00026"",
""customer_po_number"": ""34567"",
""po_date"": ""2018-08-23T00:00:00"",
""batch_code"": ""######"",
""req_ship_date"": ""2018-08-28T00:00:00"",
""currency_id"": ""USD"",
""original_invoice_number"": null,
""bill_to_id"": ""00026"",
""customer_level"": null,
""terms_code"": ""30-2"",
""distribution_code"": ""01"",
""invoice_number"": null,
""invoice_date"": ""2018-08-23T00:00:00"",
""sales_rep_id_1"": null,
""sales_rep_id_1_percent"": 100,
""sales_rep_id_1_rate"": 0,
""sales_rep_id_2_id"": ""SA"",
""sales_rep_id_2_percent"": 100,
""sales_rep_id_2_rate"": 10,
""ship_to_attention"": null,
""ship_to_address_1"": null,
""ship_to_address_2"": null,
""ship_to_region"": null,
""ship_to_country"": ""USA"",
""ship_to_postal_code"": null,
""actual_ship_date"": null,
""item_id"": ""5' Glass/Wood Table"",
""description"": ""Glass/ Wood Combo Coffee Table"",
""customer_part_no"": null,
""additional_description"": null,
""quantity_ordered"": 11,
""quantity_backordered"": 0,
""extended_price"": 6599.89,
""inventory_gl_account"": ""120000"",
""sales_gl_account"": ""400000"",
""cogs_gl_account"": ""500000"",
""sales_category"": null,
""discount_percentage"": 0,
""sales_rep_id_1"": null,
""sales_rep1_percent"": null,
""sales_rep1_rate"": null,
""sales_rep_id_2"": ""SA"",
""sales_rep2_percent"": 100,
""extended_cost"": 2655.91,
""extended_list"": [""yo""],
""item_id"": ""6' Glass/Wood Table"",
""description"": ""Glass/ Wood Combo Coffee Table"",
""customer_part_no"": null,
""additional_description"": null,
""quantity_ordered"": 11,
""quantity_backordered"": 0,
""extended_price"": 6599.89,
""inventory_gl_account"": ""120000"",
""sales_gl_account"": ""400000"",
""cogs_gl_account"": ""500000"",
""sales_category"": null,
""discount_percentage"": 0,
""sales_rep_id_1"": null,
""sales_rep1_percent"": null,
""sales_rep1_rate"": null,
""sales_rep_id_2"": ""SA"",
""sales_rep2_percent"": 100,
""extended_cost"": 2655.91,
""tax_group_id"": ""ATL"",
""tax_class_adjustment"": 0,
""tax_class_freight"": 0,
""tax_location_adjustment"": null,
""non_taxable_sales"": 6599.89,
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version);
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");