如何仅用NewtonSoft.Json将动态嵌套JSON转换为CSV?
复杂嵌套JSON转CSV的C#实现(仅用Newtonsoft.Json)
你之前的扁平化方法只适用于无数组的简单JSON,当JSON包含嵌套数组时,Descendants().OfType<JValue>().ToDictionary()会把所有字段(包括带数组索引的路径,比如orders[0].id)都作为单独列,导致CSV结构混乱,无法对应每行一个数据实体的需求。以下是可行的解决方案:
核心思路
- 递归扁平化单个JSON节点,将嵌套对象的字段用
.拼接路径,数组元素用[索引]标记路径 - 把数组中的每个元素作为CSV的一行,而非将整个JSON的所有扁平字段堆在一行
- 收集所有可能的列名,确保每行数据的列数一致
- 处理CSV的转义规则(逗号、引号、换行等特殊字符)
完整代码实现
using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Text; public static class JsonToCsvConverter { // 递归扁平化单个JToken,返回字段名与值的映射字典 private static Dictionary<string, string> FlattenJToken(JToken token, string parentPath = "") { var result = new Dictionary<string, string>(); switch (token.Type) { case JTokenType.Object: foreach (JProperty prop in token.Children<JProperty>()) { var childPath = string.IsNullOrEmpty(parentPath) ? prop.Name : $"{parentPath}.{prop.Name}"; var childDict = FlattenJToken(prop.Value, childPath); foreach (var kvp in childDict) { result[kvp.Key] = kvp.Value; } } break; case JTokenType.Array: for (int i = 0; i < token.Count(); i++) { var childPath = string.IsNullOrEmpty(parentPath) ? $"[{i}]" : $"{parentPath}[{i}]"; var childDict = FlattenJToken(token[i], childPath); foreach (var kvp in childDict) { result[kvp.Key] = kvp.Value; } } break; case JTokenType.Property: var propToken = (JProperty)token; var propPath = string.IsNullOrEmpty(parentPath) ? propToken.Name : $"{parentPath}.{propToken.Name}"; var propDict = FlattenJToken(propToken.Value, propPath); foreach (var kvp in propDict) { result[kvp.Key] = kvp.Value; } break; default: if (!string.IsNullOrEmpty(parentPath)) { result[parentPath] = token.ToString(); } break; } return result; } // 将JSON字符串转换为CSV内容 public static string ConvertJsonToCsv(string json) { var jToken = JToken.Parse(json); var rows = new List<Dictionary<string, string>>(); // 处理根节点:数组则每个元素为一行;对象则优先取内部第一个数组作为行来源 if (jToken.Type == JTokenType.Array) { foreach (var item in jToken.Children()) { rows.Add(FlattenJToken(item)); } } else if (jToken.Type == JTokenType.Object) { var firstArray = jToken.Descendants().OfType<JArray>().FirstOrDefault(); if (firstArray != null) { foreach (var item in firstArray.Children()) { rows.Add(FlattenJToken(item)); } } else { // 无数组则将整个对象作为一行 rows.Add(FlattenJToken(jToken)); } } if (rows.Count == 0) { return string.Empty; } // 收集所有唯一列名并排序 var allColumns = rows.SelectMany(row => row.Keys).Distinct().OrderBy(col => col).ToList(); // 构建CSV内容 var sb = new StringBuilder(); // 写入表头 sb.AppendLine(string.Join(",", allColumns.Select(EscapeCsvValue))); // 写入每行数据 foreach (var row in rows) { var rowValues = allColumns.Select(col => row.ContainsKey(col) ? EscapeCsvValue(row[col]) : string.Empty); sb.AppendLine(string.Join(",", rowValues)); } return sb.ToString(); } // CSV值转义处理:特殊字符用双引号包裹,内部双引号转义为两个双引号 private static string EscapeCsvValue(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } bool needsQuotes = value.Contains(',') || value.Contains('"') || value.Contains('\n') || value.Contains('\r'); if (!needsQuotes) { return value; } return $"\"{value.Replace("\"", "\"\"")}\""; } }
示例使用
string json = @" { ""orders"": [ { ""id"": 1, ""customer"": { ""name"": ""Alice"", ""email"": ""alice@example.com"" }, ""items"": [ {""product"": ""Laptop"", ""price"": 999.99}, {""product"": ""Mouse"", ""price"": 25.50} ] }, { ""id"": 2, ""customer"": { ""name"": ""Bob"", ""email"": ""bob@example.com"" }, ""items"": [ {""product"": ""Keyboard"", ""price"": 49.99} ] } ] }"; string csv = JsonToCsvConverter.ConvertJsonToCsv(json); Console.WriteLine(csv);
关键说明
- 数组元素会被转换为独立行,嵌套字段通过路径拼接(如
customer.name、items[0].product)作为列名 - 自动处理CSV格式转义,避免特殊字符破坏结构
- 兼容纯对象、纯数组、对象嵌套数组等多种JSON结构
内容的提问来源于stack exchange,提问作者scynthia
相关产品推荐
相关产品推荐

