Я пытаюсь использовать код от этот ответ С# до Преобразовать вложенный JSON в CSV, но мой проект VB.NET.
Я пробовал несколько онлайн-конвертеров, но без особого успеха. Версия C# работает отлично, но версии VB.NET выдают InvalidCastException
. В чем может быть проблема?
Мой JSON:
{
"Response": [
[
{
"id": 136662306,
"symbol": "aaa",
"status": "ACTIVE",
"base": "731.07686321",
"amount": "6.95345994",
"timestamp": "1524781083.0",
"swap": "0.0",
"pl": "5127.4352653395923394"
},
{
"id": 137733525,
"symbol": "bbb",
"status": "ACTIVE",
"base": "636.75093128",
"amount": "1.1",
"timestamp": "1531902193.0",
"swap": "0.0",
"pl": "687.800226608"
}
]
]
}
Моя версия С#, которая работает
JObject obj = JObject.Parse(json);
// Collect column titles: all property names whose values are of type JValue, distinct, in order of encountering them.
var values = obj.DescendantsAndSelf()
.OfType<JProperty>()
.Where(p => p.Value is JValue)
.GroupBy(p => p.Name)
.ToList();
var columns = values.Select(g => g.Key).ToArray();
// Filter JObjects that have child objects that have values.
var parentsWithChildren = values.SelectMany(g => g).SelectMany(v => v.AncestorsAndSelf().OfType<JObject>().Skip(1)).ToHashSet();
// Collect all data rows: for every object, go through the column titles and get the value of that property in the closest ancestor or self that has a value of that name.
var rows = obj
.DescendantsAndSelf()
.OfType<JObject>()
.Where(o => o.PropertyValues().OfType<JValue>().Any())
.Where(o => o == obj || !parentsWithChildren.Contains(o)) // Show a row for the root object + objects that have no children.
.Select(o => columns.Select(c => o.AncestorsAndSelf()
.OfType<JObject>()
.Select(parent => parent[c])
.Where(v => v is JValue)
.Select(v => (string)v)
.FirstOrDefault())
.Reverse() // Trim trailing nulls
.SkipWhile(s => s == null)
.Reverse());
// Convert to CSV
var csvRows = new[] { columns }.Concat(rows).Select(r => string.Join(",", r));
var csv = string.Join("\n", csvRows);
Моя версия VB.NET дает следующее исключение:
System.InvalidCastException: 'Unable to cast object of type 'WhereSelectEnumerableIterator`2
Dim obj As JObject = Nothing
obj = JObject.Parse(json)
Dim values = obj.DescendantsAndSelf().
OfType(Of JProperty)().
Where(Function(p) TypeOf p.Value Is JValue).
GroupBy(Function(p) p.Name).ToList()
Dim columns = values.[Select](Function(g) g.Key).ToArray()
Dim parentsWithChildren = values.SelectMany(Function(g) g).
SelectMany(Function(v) v.AncestorsAndSelf().
OfType(Of JObject)().Skip(1)).ToHashSet()
Dim rows = obj.DescendantsAndSelf().
OfType(Of JObject)().
Where(Function(o) o.PropertyValues().
OfType(Of JValue)().Any()).
Where(Function(o) o = obj OrElse Not parentsWithChildren.Contains(o)).
[Select](Function(o) columns.[Select](Function(c) o.AncestorsAndSelf().
OfType(Of JObject)().
[Select](Function(parent) parent(c)).
Where(Function(v) TypeOf v Is JValue).
[Select](Function(v) CStr(v)).
FirstOrDefault()).
Reverse().
SkipWhile(Function(s) s Is Nothing).
Reverse())
Dim csvRows = {columns}.Concat(rows).[Select](Function(r) String.Join(",", r)) ' HERE IS WHERE THE EXCEPTION OCCURS
Dim csv = String.Join(vbLf, csvRows)
Исключение
{"Unable to cast object of type 'WhereSelectEnumerableIterator`2[Newtonsoft.Json.Linq.JObject,System.Collections.Generic.IEnumerable`1[System.String]]' to type 'System.Collections.Generic.IEnumerable`1[System.String[]]'."}
Возможно, вы могли бы добавить новую библиотеку на С#, и решение сможет работать с обоими языками одновременно.
Кстати, если вы не знаете, является ли корневой контейнер JSON массивом или объектом, используйте JToken.Parse
, а затем выполните приведение к JContainer
, чтобы сделать DescendantsAndSelf()
. Подробнее см. в JSON.NET: зачем вообще использовать JToken?.
Я перешел по ссылке, которую вы даете для кода С#, но я не вижу ничего, что соответствовало бы коду VB.NET, который вы показываете. Не могли бы вы предоставить код C#?
Я только что отредактировал и добавил весь необходимый код
Упрощенный VB.NET тоже
Вам нужно сделать { columns.AsEnumerable() }.Concat(rows)
вместо просто {columns}.Concat(rows)
, см. dotnetfiddle.net/jFXvxw. Я не могу объяснить, почему, однако, кажется, что вывод типа VB.NET делает что-то странное при использовании метода расширения Linq Concat
для объединения массива строк с ленивым перечислением строк. Добавление в ToEnumerable()
исправляет вывод типа (хотя на самом деле это ничего не делает, это просто преобразование). См. dotnetfiddle.net/jFXvxw
Простой минимальный воспроизводимый пример проблемы, которая не имеет ничего общего с JSON: рабочий c# здесь: dotnetfiddle.net/vcZsjV, нерабочий vb.net здесь: dotnetfiddle.net/k6Z5tE, исправлен vb.net здесь: dotnetfiddle.net/sbi3oA Не понимаю, зачем добавлять AsEnumerable()
для изменения 2d зубчатого массива строк в массив перечисляемых строк ничего не меняет. Я почти склонен задать отдельный вопрос об этом.
@dbc Да, похоже на ошибку. Если вы перед вызовом IEnumerable(Of IEnumerable(Of String))
приведете зубчатый массив к .Concat()
, он будет работать нормально (даже несмотря на то, что все, что мы сделали, — это приведение неявный) (что, по-моему, в основном и делает AsEnumberable()
?). Я предполагаю, что разрешение перегрузки не может найти соответствующую перегрузку метода расширения?
Вы правы, просто добавление DirectCast
решает проблему: DirectCast(columns, IEnumerable(Of IEnumerable(Of String))).Concat(rows)
, см. dotnetfiddle.net/tWnzTA. Довольно загадочный и, возможно, стоящий второго вопроса. Вы хотите это спросить?
@dbc Умм, конечно! Позвольте мне собрать пример.
@AhmedAbdelhameed тем временем я добавил ответ здесь, так что этот вопрос может быть решен.
Чтобы {columns}.Concat(rows)
работал, кажется, вам нужно добавить явный вызов AsEnumerable()
, чтобы убедиться, что тип TSource
для Enumerable.Concat(IEnumerable<TSource>, IEnumerable<TSource>)
выводится правильно:
Dim csvRows = { columns.AsEnumerable() }.Concat(rows) _
.Select(Function(r) String.Join(",", r))
Исправлена рабочий пример №1 здесь.
DirectCast({columns}, IEnumerable(Of IEnumerable(Of String)))
также работает, как указано в Комментарии автором Ахмед Абдельхамид:
Dim csvRows = DirectCast({columns}, IEnumerable(Of IEnumerable(Of String))).Concat(rows) _
.Select(Function(r) String.Join(",", r))
Исправлена рабочий пример #2 здесь.
Вызов Enumerable.Concat(Of IEnumerable(Of String))
явно, без использования логических выводов, также работает:
Dim csvRows = Enumerable.Concat(Of IEnumerable(Of String))({columns}, rows) _
.Select(Function(r) String.Join(",", r))
Исправлена рабочий пример №3 здесь.
Таким образом, весь ваш код должен выглядеть так:
Dim obj As JObject = JObject.Parse(json)
Dim values = obj.DescendantsAndSelf().OfType(Of JProperty)().Where(Function(p) TypeOf p.Value Is JValue).GroupBy(Function(p) p.Name).ToList()
Dim columns = values.[Select](Function(g) g.Key).ToArray()
Dim parentsWithChildren = values.SelectMany(Function(g) g).SelectMany(Function(v) v.AncestorsAndSelf().OfType(Of JObject)().Skip(1)).ToHashSet()
Dim rows = obj.DescendantsAndSelf() _
.OfType(Of JObject)() _
.Where(Function(o) o.PropertyValues().OfType(Of JValue)().Any()) _
.Where(Function(o) o.Equals(obj) OrElse Not parentsWithChildren.Contains(o)) _
.Select(Function(o) columns.Select(Function(c) _
o.AncestorsAndSelf() _
.OfType(Of JObject)() _
.Select(Function(parent) parent(c)) _
.OfType(Of JValue)() _
.Select(Function(v) CStr(v)) _
.FirstOrDefault()) _
.Reverse() _
.SkipWhile(Function(s) s Is Nothing) _
.Reverse())
Dim csvRows = { columns.AsEnumerable() }.Concat(rows) _
.Select(Function(r) String.Join(",", r))
Dim csv = String.Join(vbLf, csvRows)
Я не знаю VB от моей задницы, но эта строка не ошибается
If arr Is Nothing Then obj = JObject.Parse(arr.ToString())
. Если массив равен нулю, тогда array.tostring()???