c# - 如何访问 JArray 中不同级别的嵌套 JObject 的值以将它们导出到 CSV 文件?
问题描述
我是 C#、REST API 和 JSON 的新手,所以请多多包涵。我已经搜索了数百页,并试图弄清楚这一切是如何运作的。对于上下文,我使用类似搜索http://mylink.com/rest/api/2/search?jql=project%3DBULK
并获取问题列表从 JIRA REST API 中提取问题。我的程序引入的 JSON 结构如下(这已从实际 JSON 中简化):
编辑(04/04/2021):用新的用例更新了下面的 JSON。
[
{
"key": "FAKE-5402",
"fields": {
"summary": "I need access to blah",
"customfield_18302": [{
"self": "fake.url.com",
"value": "I need this"
}, {
"self": "fake.url2.com",
"value": "I need this also"
}]
}
},
{
"key": "FAKE-5450",
"fields": {
"summary": "Example number 2",
"customfield_18302": [{
"self": "fake.url3.com",
"value": "I need this"
}, {
"self": "fake.url4.com",
"value": "I need this also"
}]
}
}
]
我目前正在研究一种获取此 JSON 数据的方法,找到用户正在搜索的键并将其以 CSV 格式打印出来,以便稍后写入文件。这是我的formatAsCSV()
功能:
public void formatAsCSV()
{
try
{
var dynObj = JsonConvert.DeserializeObject<dynamic>(this.jsonData);
String issues = dynObj["issues"].ToString();
JArray issuesArray = JArray.Parse(issues);
List<String> columnNames = new List<String>()
{"key", "summary"};
String headerRow = "";
foreach (String columnName in columnNames)
{
headerRow += columnName + ", ";
}
headerRow = headerRow.TrimEnd(' ');
headerRow = headerRow.TrimEnd(',');
headerRow += "\n";
String dataRows = "";
foreach (var record in issuesArray)
{
String thisRecord = "";
foreach (String columnName in columnNames)
{
thisRecord += record[columnNames] + ", ";
}
thisRecord = thisRecord.TrimEnd(' ');
thisRecord = thisRecord.TrimEnd(',');
thisRecord += "\n";
dataRows += thisRecord;
}
this.csvData = headerRow + dataRows;
Console.WriteLine("\ncsvData: ");
Console.WriteLine(this.csvData);
}
catch (Exception ex)
{
Console.WriteLine("Error in formatAsCSV: " + ex);
this.errorsOccurred = true;
}
}
我在上面的代码中遇到的问题(我已经尝试了几天来解决这个问题......)是它将所有问题作为单独的对象引入。因此,当代码循环通过时,我可以获得“key”、“expand”、“id”、“self”,因为它们没有嵌套。当我需要诸如“摘要”和“问题类型”->“名称”之类的内容时,相同的代码不会抓取嵌套在“字段”下的内容。我不知道从哪里开始获取这些信息。
这是我从这样的东西中得到的输出(注意第一个逗号后缺少摘要):
csvData:
key, summary
BULK-62,
BULK-47,
如果有人有任何想法,请告诉我,因为我已经没有东西可以尝试了。这里的大多数问题都以 JObject 开头,但我不断收到错误,“问题”是我拉的主要内容,它是一个数组,而不是一个对象,所以不知道如何处理这个问题。我知道它可以以某种方式工作,因为 JIRA 允许 CSV 通过手动过程提取。我希望有一种方法可以保持我拥有的过程(它来自遵循 YT 教程),所以我不必重写我已经完成的所有内容,但我理解它是否归结为这一点。提前致谢!
解决方案
我最近回答了一个类似的问题,提问者想从嵌套中提取所有属性并将这些值变成 CSV 列。在您的情况下,您希望从对象及其子项中选择不同级别的属性。JObject
有效解决此问题的关键是利用该SelectToken()
方法对您有利。此方法接受JsonPath表达式,它允许您查询 aJToken
以查找其后代之一。在最简单的形式中,这只是一个以点分隔的字符串,例如fields.summary
.
借用另一个问题,我会做两个辅助方法。第一种方法将接受JObjects
表示行项目的列表以及Dictionary<string, string>
将列名映射到将用于从每个项目中检索值的相应路径。第二种方法将接受一个值列表并将其转换为 CSV 行。
public static class JsonHelper
{
public static string ToCsv(this IEnumerable<JObject> items, Dictionary<string, string> columnPathMappings)
{
if (items == null || columnPathMappings == null)
throw new ArgumentNullException();
var rows = new List<string>();
rows.Add(columnPathMappings.Keys.ToCsv());
foreach (JObject item in items)
{
rows.Add(columnPathMappings.Values.Select(path => item.SelectToken(path)).ToCsv());
}
return string.Join(Environment.NewLine, rows);
}
public static string ToCsv(this IEnumerable<object> values)
{
const string quote = "\"";
const string doubleQuote = "\"\"";
return string.Join(",", values.Select(v =>
v != null ? string.Concat(quote, v.ToString().Replace(quote, doubleQuote), quote) : string.Empty
));
}
}
有了这些方法,创建 CSV 字符串所需要做的就是设置映射,然后解析 JSON,从中提取 items 数组并调用帮助程序来完成剩下的工作:
var columnPathMappings = new Dictionary<string, string>
{
{ "key", "key" },
{ "summary", "fields.summary" }
};
string csv = JArray.Parse(json).Cast<JObject>().ToCsv(columnPathMappings);
这是一个工作演示:https ://dotnetfiddle.net/zzBpbT
如果您的 JSON 有一个内部数组,那么这意味着单行中的特定列可能有多个值。鉴于 CSV 是一个扁平结构,在插入 CSV 之前,需要将多个值连接在一起形成一个以换行符分隔的字符串。您可以像这样修改上面的代码:
- 在第一种方法中更改
SelectToken
为SelectTokens
. 这将允许它为单个路径提取多个值,而不仅仅是一个。 - 在第二种方法中,检查并处理对象 in
IEnumerable<object> values
本身是一个IEnumerable<Jtoken>
. 在这种情况下,将 JToken 连接到一个以换行符分隔的字符串中,然后像以前一样处理该字符串。
修改后的代码如下所示:
public static class JsonHelper
{
public static string ToCsv(this IEnumerable<JObject> items, Dictionary<string, string> columnPathMappings)
{
if (items == null || columnPathMappings == null)
throw new ArgumentNullException();
var rows = new List<string>();
rows.Add(columnPathMappings.Keys.ToCsv());
foreach (JObject item in items)
{
rows.Add(columnPathMappings.Values.Select(path => item.SelectTokens(path)).ToCsv());
}
return string.Join(Environment.NewLine, rows);
}
public static string ToCsv(this IEnumerable<object> values)
{
const string quote = "\"";
const string doubleQuote = "\"\"";
return string.Join(",", values.Select(v =>
{
if (v != null)
{
if (v is IEnumerable<JToken> e)
{
v = string.Join(Environment.NewLine, e.Select(t => t.ToString()));
}
return string.Concat(quote, v.ToString().Replace(quote, doubleQuote), quote);
}
return string.Empty;
}));
}
}
最后一步是您需要指定一个路径,该路径将获取数组中的子值。为此,您可以*
在数组索引的路径中使用通配符。所以你的映射看起来像这样:
var columnPathMappings = new Dictionary<string, string>
{
{ "key", "key" },
{ "summary", "fields.summary" },
{ "customfield_18302", "fields.customfield_18302[*].value" },
};
在这里工作演示:https ://dotnetfiddle.net/IfB8sw
推荐阅读
- javascript - 嵌套对象的键是否存储在对象中?
- batch-file - 在 MinGW 中处理 32 个窗口限制(Windows 批处理文件)
- html - Bootstrap4 网格系统 - SM+ 中的 Flex 和 XS 中的 Row>Col
- css - 如何在有角材料的一行上制作标题和日期
- curl - CURLOPT_REQUESTFIELDS
- sql - 将行转入未知数量的列
- gcc - 为什么必须为特定目标配置 gnu binutils。下面发生了什么
- spring - 微服务还是连接到两个数据库?
- google-chrome - 从不同域调用身份验证 API 不会设置 cookie
- android - 离子角度奇怪的问题 - 自定义表单验证器未在 android 上被调用