如何访问 JArray 中嵌套 JObject 的不同级别的值以将它们导出到 CSV 文件?
How can I access values at different levels of nested JObjects in a JArray to export them to a CSV file?
我是 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 教程),这样我就不必重写我已经完成的所有内容,但我理解是否归结为这一点。提前致谢!
您尝试访问的字段嵌套在 Json 对象的更深处。
请注意 Json 结构。字段“summary”嵌套在对象“fields”中,因此要访问它,您需要先访问字段对象。
这意味着,您必须重新考虑使用列名列表的方法。
有一个 I recently where the asker wanted to extract all properties from a nested JObject
并将这些值变成 CSV 列。在您的情况下,您想从对象及其子项中挑选不同级别的属性。
有效解决这个问题的关键是使用 SelectToken()
method to your advantage. This method accepts a JsonPath expression, which lets you query a JToken
找到它的后代之一。在最简单的形式中,这只是一个点分隔的字符串,例如 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,从中提取项目数组并调用助手来完成剩下的工作:
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
。这将允许它为单个路径而不是一个路径提取多个值。
- 在第二种方法中,检查并处理
IEnumerable<object> values
中的对象本身是IEnumerable<Jtoken>
的可能性。在这种情况下,将 JTokens 连接成一个换行符分隔的字符串,然后像以前一样处理该字符串。
修改后的代码如下所示:
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
我是 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 教程),这样我就不必重写我已经完成的所有内容,但我理解是否归结为这一点。提前致谢!
您尝试访问的字段嵌套在 Json 对象的更深处。 请注意 Json 结构。字段“summary”嵌套在对象“fields”中,因此要访问它,您需要先访问字段对象。
这意味着,您必须重新考虑使用列名列表的方法。
有一个 JObject
并将这些值变成 CSV 列。在您的情况下,您想从对象及其子项中挑选不同级别的属性。
有效解决这个问题的关键是使用 SelectToken()
method to your advantage. This method accepts a JsonPath expression, which lets you query a JToken
找到它的后代之一。在最简单的形式中,这只是一个点分隔的字符串,例如 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,从中提取项目数组并调用助手来完成剩下的工作:
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
。这将允许它为单个路径而不是一个路径提取多个值。 - 在第二种方法中,检查并处理
IEnumerable<object> values
中的对象本身是IEnumerable<Jtoken>
的可能性。在这种情况下,将 JTokens 连接成一个换行符分隔的字符串,然后像以前一样处理该字符串。
修改后的代码如下所示:
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