-3

I am trying to use code from this c# answer to Convert nested JSON to CSV but my project is VB.NET. I tried few online converters but without much success. C# version works great but the VB.NET versions throw an InvalidCastException. What might be the problem ?

My 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"
      }
    ]
  ]
}

My C# Version that works

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);

My VB.NET version gives the following exception:

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)

Exception

{"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[]]'."}
dbc
  • 104,963
  • 20
  • 228
  • 340
SparcU
  • 742
  • 1
  • 7
  • 27
  • 3
    I don't know VB from my arse, but is this line not wrong `If arr Is Nothing Then obj = JObject.Parse(arr.ToString())`. If array is null then array.tostring()??? – Simon Wilson May 19 '19 at 20:41
  • 1
    Maybe you could add a new library in C# and the solution can work with both lang simultaneously – Mate May 19 '19 at 21:03
  • Incidentally, if you don't know whether the JSON root container is an array or an object, use `JToken.Parse`, and then cast to a `JContainer` to do `DescendantsAndSelf()`. See [JSON.NET: Why Use JToken--ever?](https://stackoverflow.com/a/38212978/3744182) for details. – dbc May 19 '19 at 21:06
  • I followed the link you give for the C# code, but I do not see anything that would match the VB.NET code that your are showing. Could you provide the C# code? – RobertBaron May 19 '19 at 21:26
  • I just edited and added all the needed code – SparcU May 19 '19 at 22:10
  • Simplified VB.NET too – SparcU May 19 '19 at 22:16
  • 1
    You need to do `{ columns.AsEnumerable() }.Concat(rows)` instead of just `{columns}.Concat(rows)`, see https://dotnetfiddle.net/jFXvxw. I can't really explain why though, seems like VB.NET's type inferencing does something odd when using Linq's `Concat` extension method to combine an array of strings with a lazy enumerable of strings. Adding in the `ToEnumerable()` fixes the type inferencing (even though it doesn't really do anything, it's just an upcast.) See https://dotnetfiddle.net/jFXvxw – dbc May 20 '19 at 00:35
  • Simple [mcve] of the problem that has nothing to do with JSON: working c# here: https://dotnetfiddle.net/vcZsjV, nonworking vb.net here: https://dotnetfiddle.net/k6Z5tE, fixed vb.net here: https://dotnetfiddle.net/sbi3oA I don't understand why adding `AsEnumerable()` to change the 2d jagged array of strings to an array of enumerables of strings changes anything. I'm almost inclined to ask a separate question about this. – dbc May 20 '19 at 01:05
  • @dbc Yep, seems like a bug. If you cast the jagged array to `IEnumerable(Of IEnumerable(Of String))` before calling `.Concat()`, it works just fine (even though all we did is an _emplicit_ cast)(which is basically what `AsEnumberable()` does, I think?). I guess the overload resolution fails to find the appropriate extension method overload? – 41686d6564 stands w. Palestine May 20 '19 at 01:43
  • You're right, just throwing in a `DirectCast` solves the problem: `DirectCast(columns, IEnumerable(Of IEnumerable(Of String))).Concat(rows)`, see https://dotnetfiddle.net/tWnzTA. Rather mysterious, and possibly worth a second question. You want to ask it? – dbc May 20 '19 at 01:47
  • @dbc Umm, sure! Let me put together an example. – 41686d6564 stands w. Palestine May 20 '19 at 02:00
  • @AhmedAbdelhameed in the meantime I added an answer here, so this question can get resolved. – dbc May 20 '19 at 02:18

1 Answers1

1

In order for {columns}.Concat(rows) to work, it seems you need to add an explicit call to AsEnumerable() in order to make sure the type TSource for Enumerable.Concat(IEnumerable<TSource>, IEnumerable<TSource>) is inferred correctly:

Dim csvRows = { columns.AsEnumerable() }.Concat(rows) _
                .Select(Function(r) String.Join(",", r))

Fixed fiddle #1 here.

A DirectCast({columns}, IEnumerable(Of IEnumerable(Of String))) also seems to work, as mentioned in comments by Ahmed Abdelhameed:

Dim csvRows = DirectCast({columns}, IEnumerable(Of IEnumerable(Of String))).Concat(rows) _
                .Select(Function(r) String.Join(",", r))

Fixed fiddle #2 here.

Calling Enumerable.Concat(Of IEnumerable(Of String)) explicitly, without making use of inferencing, works also:

Dim csvRows = Enumerable.Concat(Of IEnumerable(Of String))({columns}, rows) _
                .Select(Function(r) String.Join(",", r))

Fixed fiddle #3 here.

Thus your entire code should look like:

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)        
dbc
  • 104,963
  • 20
  • 228
  • 340