You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅用NewtonSoft.Json将动态嵌套JSON转换为CSV?

复杂嵌套JSON转CSV的C#实现(仅用Newtonsoft.Json)

你之前的扁平化方法只适用于无数组的简单JSON,当JSON包含嵌套数组时,Descendants().OfType<JValue>().ToDictionary()会把所有字段(包括带数组索引的路径,比如orders[0].id)都作为单独列,导致CSV结构混乱,无法对应每行一个数据实体的需求。以下是可行的解决方案:

核心思路

  1. 递归扁平化单个JSON节点,将嵌套对象的字段用.拼接路径,数组元素用[索引]标记路径
  2. 把数组中的每个元素作为CSV的一行,而非将整个JSON的所有扁平字段堆在一行
  3. 收集所有可能的列名,确保每行数据的列数一致
  4. 处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 02:30:44