Deserialise JSON message of two types - json

I try to consume web service returning JSON messages. Each method returns relevant JSON message (typically an array) or JSON error message (not an array).
Examples of UserList and Error messages:
[
{
"id":1,
"login":"john1",
"full_name":"John Smith"
},
{
"id":2,
"login":"anne",
"full_name":"Anne Steward"
}
]
{
"success":false,
" message":"Unknown login"
}
Because code does not know which message to expect, I have created two classes where UserList inherits from BaseResponse (an error message class). I have used inheritance, as all other methods like GetCarList, etc. are suitable of returning an error message as well. So no need to declare error fields multiple times. That had to be smart - I will always get or UserList fields or error message fields in the same object.
Public Class BaseResponse
Public success As Boolean
Public message As String
End Class
Public Class UserListResponse
Inherits BaseResponse
Public id As Integer
Public login As String
Public full_name As String
End Class
The problem is, that UserListResponse JSON message is an array, while ErrorMessage JSON is not an array. So deserialising will work when JSON will be UserList but will not work when JSON will be an error message:
JsonConvert.DeserializeObject(Of List(Of UserListResponse))(ReceivedJSONMessage)
Any ideas how to tackle this in smart way?

If you are certain that both of your return classes are working for deserializing the json correctly you just need in essence: "Am I a list or a string?" to ask for one path or the other.
EDIT 8-30
Okay so I missed that you do not KNOW till after it was executed, my bad. You can do the logic inside of Sub Routine instead of a Function then that does the logic directly on examining a generic object passed instead. Similar to this.
Private Sub DetermineReturn(obj As Object)
If obj.GetType = GetType(String) Then
Console.WriteLine(CType(obj, String))
ElseIf obj.GetType = GetType(List(Of String)) Then
CType(obj, List(Of String)).ForEach(Sub(x) Console.WriteLine(x))
Else
Console.WriteLine("UnKnOwN!1!")
End If
End Sub
Sub Main()
Dim testError = "Oh Shoot Error!"
Dim GoodStuff = New List(Of String)({"I", "am", "a", "list"})
Console.WriteLine("FirstExample")
DetermineReturn(testError)
Console.WriteLine()
Console.WriteLine("SecondExample")
DetermineReturn(GoodStuff)
Console.WriteLine()
Console.WriteLine("ThirdExample")
DetermineReturn(1)
Console.ReadLine()
End Sub
Essentially You would just do the work of either transforming your JSON inside directly of the method 'DetermineReturn' instead of Console.WriteLines. I am interpreting the 'obj' passed in, using reflection to get it's type, and then it is safe to be cast as that type. So you would put the argument of your JSON return in the method like DetermineReturn((YourJSONResult)) and then inside the method do whatever transforms you need on the object.

Related

How to deserialize Json with fix named elements dynamically in vb.net?

I’m on the way to implement an interface to the Ameritrade Rest API in a vb.net application (with httpclient).
Amongst other things, I have to query quotes from a ticker list (e.g. AMD,MSFT,AMZN, ....).
The call of the API works without problems, I get a valid Json back, but the Json is not given back in a way, I would expect.
I now search the best way to handle that problem...
This is not the first interface to a Rest API, I have implemented.
Normally, I implement a corresponding data class in vb.net and then use JsonConvert (from Newtonsoft) to deserialize the Json string into my data class.
Example:
Dim oObject As New DataClass
oObject = JsonConvert.DeserializeObject(Of DataClass)(JsonString)
whereby DataClass is the vb.net class that is defined according to the data in the Json string.
Problem:
The ticker symbol-list to query is dynamic and can change from api call to api call.
If I - e.g. - query AMD and MSFT in a call, I get back (cut to only a few fields) the following Json:
{
"AMD": {
"assetType": "EQUITY",
"symbol": "AMD",
"description": "Advanced Micro Devices, Inc. - Common Stock",
"bidPrice": 92.11
},
"MSFT": {
"assetType": "EQUITY",
"symbol": "MSFT",
"description": "Microsoft Corporation - Common Stock",
"bidPrice": 243.1
}
}
To be able to deserialize the Json, I would have to implement the following DataClass:
Public Class DataClass
Public Property AMD As AMD
Public Property MSFT As MSFT
End Class
Public Class AMD
Public Property assetType As String
Public Property symbol As String
Public Property description As String
Public Property bidPrice As Double
End Class
Public Class MSFT
Public Property assetType As String
Public Property symbol As String
Public Property description As String
Public Property bidPrice As Double
End Class
This would work but is absolutely static and does not make any sense, as I would have to implement a (identical) class for any ticker, I maybe want to query in the feature.
I would expect to get back a dynamic list so that I could implement the class as following:
Public Class DataClass
Public Property TickerDetails As List(Of TickerDetail)
End Class
Public Class TickerDetail
Public Property assetType As String
Public Property symbol As String
Public Property description As String
Public Property bidPrice As Double
End Class
This way, I would be able to deserialize in a List of TickerDetails and the go thru the list (no matter, which symbols I queried).
But, I can’t change, what I get back over the API...
Question:
What is the best way to handle this problem?
You should create a class to represent the a generic stock and then use DeserializeObject to deserialize it into a Dictionary(Of String, [classname]) where the Key represents the stock symbol and the value represents the class.
Take a look at this example:
Public Class Stock
Public Property assetType As String
Public Property symbol As String
Public Property description As String
Public Property bidPrice As Double
End Class
'...
Dim stocks = JsonConvert.DeserializeObject(Of Dictionary(Of String, Stock))(response)
Example: Live Demo
First thanks for the comments.
I ended up to do it completely different now...
I had further problems with the Ameritrade API:
Some fields are named with leading numbers (52WkHigh and 52WkLow) and
vb.net dev's know, that VB.net don't like properties in classes that
are named with a leading number
So I had to "patch" the received Json data and change the names on the fly to other names ("52WkHigh" to "dble52WkHigh" and "52WkLow" to "dble52WkLow") to be able to deserialize
over the data class, what is not nice
Further, I finally need the data (as fast as possible) in a data table and had "a long way to go":
get data -> deserialize to the data class -> walk thru the data class and overtake the data in the data table.
So.. my new solution (with JObject):
Note: needs:
Imports Newtonsoft.Json.Linq
Code snippets:
Create data table in memory:
Dim dtErgebnis As New DataTable
Dim drTemp As DataRow
With dtErgebnis.Columns
.Add("symbol", System.Type.GetType("System.String"))
.Add("lastPrice", System.Type.GetType("System.Double"))
.Add("lastSize", System.Type.GetType("System.Int32"))
.Add("quoteTime", System.Type.GetType("System.DateTime")) ' Note: is a Long in Json
...
End With
Parse the Json-String and fill the datatable:
get the data over httpclient (in JsonString)...
Dim oJson As JObject = JObject.Parse(JsonString) ' creates children tokens
Dim results As List(Of JToken) = oJson.Children().ToList
For Each item As JProperty In results
item.CreateReader()
drTemp = dtErgebnis.NewRow() ' create a new row to data table in memory
' Fill the fields
drTemp("symbol") = item.Value("symbol")
drTemp("lastPrice") = item.Value("lastPrice")
drTemp("lastSize") = item.Value("lastSize")
drTemp("quoteTime") = GetUTCDateFromTimeStamp(item.Value("quoteTimeInLong")).AddHours(1) ' original Long
...
' Add the new row to the data table
dtErgebnis.Rows.Add(drTemp)
' Save the changes
dtErgebnis.AcceptChanges()
Next
Additional note: The Ameritrade API gives back the time stamps as long (additional hurdle), but I (and I think also you;-) want it as datetime.
Therefore the Long (I think this data type comes from Java/Unix) has to be "translated" to datetime = vb.net function GetUTCDateFromTimeStamp below:
Public Function GetUTCDateFromTimeStamp(TimeStamp As Long) As DateTime
Static startTime As New DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
Return startTime.AddMilliseconds(TimeStamp)
End Function
Additional note:
As I want to have the Swiss time, I add one hour to the UTC time.
So.. this a real good solution for me (exactly for the Ameritrade API).
And.. it's blazing fast... (I get 19 tickers with all fields and show the result (data table) in a data grid).
All together took < 1 Second ("felt" about 500 ms)
Hope this helps somebody...

Convert JSON error result to String in vb.net

I have a vb.net program that posts a JSON to a Web API and returns a Result.
When the API returns an Error I need to create a Sting of the Error to display in the Program.
I am using Newtonsoft.Json
The Error that is Returned is.
{"message":"One or more input exceptions have occurred.","errors":[{"message":"\"%fieldName\" is required. Enter and try again.","parameters":{"fieldName":"name"}},{"message":"\"%fieldName\" is required. Enter and try again.","parameters":{"fieldName":"base64_encoded_data"}},{"message":"\"%fieldName\" is required. Enter and try again.","parameters":{"fieldName":"type"}}],"trace":"TraceMessageHasBeenDeleted {main}"}
I have Extracted the Message by
Dim responseFromAPI As HttpResponseMessage
Dim rawResponseFromAPI As String
Dim responseFromAPIStream As Stream
Dim responseFromAPIReader As StreamReader
Dim responseFromAPIObject As JObject
' Check Result
If responseFromAPI.IsSuccessStatusCode = False Then
' Add Error to Error Collection
responseFromAPIStream = Await responseFromAPI.Content.ReadAsStreamAsync()
responseFromAPIReader = New StreamReader(responseFromAPIStream)
rawResponseFromAPI = responseFromAPIReader.ReadToEnd()
responseFromAPIObject = JObject.Parse(rawResponseFromAPI)
' Check for Message
Console.WriteLine(responseFromAPIObject("message").ToString())
CustomErrorCollection.AddError(responseFromAPIObject("message").ToString())
End If
This returns the Message "One or more input exceptions have occurred."
But I also need to extract all the "Messages" from in the "errors" section and replace the "%fieldName" with the parameter of "fieldName"
So the Final result I am looking for is a Multi line string.
"
One or more input exceptions have occurred.
name is required. Enter and try again.
base64_encoded_data is required. Enter and try again.
type is required. Enter and try again."
There is probably a very simple way to achieve this but after a couple of hours searching and experimenting I haven't got anywhere.
Many Thanks
To assist with navigating deserialized Json, it's best to make objects which represent your json properties and derserialize into those.
{
"message":"One or more input exceptions have occurred.",
"errors":[
{
"message":"\"%fieldName\" is required. Enter and try again.",
"parameters":{
"fieldName":"name"
}
},
{
"message":"\"%fieldName\" is required. Enter and try again.",
"parameters":{
"fieldName":"base64_encoded_data"
}
},
{
"message":"\"%fieldName\" is required. Enter and try again.",
"parameters":{
"fieldName":"type"
}
}
],
"trace":"TraceMessageHasBeenDeleted {main}"
}
Looking at your json example, you have three (3) main properties Message, Errors, and Trace. You can define an object that holds these properties and call
JsonConvert.DeserializeObject(Of YourRepresentativeObject)(yourJsonString)
This would look like:
Public Class ErrorResponse
Public Property Message As String
Public Property Errors As List(Of ErrorDefinition)
Public Property Trace As String
End Class
Public Class ErrorDefinition
Public Property Message As String
Public Property Parameters As Parameter
End Class
Public Class Parameter
Public Property FieldName As String
End Class
And your code to deserialize and utilize would be:
Dim responseFromAPIObject = JsonConvert.DeserializeObject(Of ErrorResponse)(rawResponseFromAPI)
Dim builder As New StringBuilder()
builder.AppendLine(responseFromAPIObject.Message) 'Get the main message
For Each definition In responseFromAPIObject.Errors 'Get the error definitions
'replace the placeholder with the parameter fieldName
builder.AppendLine(definition.Message.Replace("%fieldName", definition.Parameters.FieldName))
Next
Console.WriteLine(builder.ToString)
Final Output:
One or more input exceptions have occurred.
"name" is required. Enter and try again.
"base64_encoded_data" is required. Enter and try again.
"type" is required. Enter and try again.

Deserializing JSON into a List (or other object type)

I am having issues deserializing some JSON into an object type that I can work with.
I have been playing around with multiple different ways to deserialize something, but I cannot get any of them to work. I am trying, currently, to use the Newtonsoft.Json deserializer.
Public Class ServerRecord
Inherits Record
<Newtonsoft.Json.JsonProperty("sys_class_name")>
Public Property sys_class_name As String
Get
End Get
Set(value As String)
End Set
End Property
<Newtonsoft.Json.JsonProperty("host_name")>
Public Property host_name As String
Get
End Get
Set(value As String)
End Set
End Property
<Newtonsoft.Json.JsonProperty("u_recovery_time_achievable")>
Public Property u_recovery_time_achievable As String
Get
End Get
Set(value As String)
End Set
End Property
End Class
Dim lstSNServersList As New List(Of ServerRecord)
Dim objServiceNowTableAPIClient As TableAPI.TableAPIClient(Of ServerRecord)
Dim objServiceNowRESTQueryResponse As RESTQueryResponse(Of ServerRecord)
objServiceNowTableAPIClient = New TableAPIClient(Of wServerRecord)(strServiceNowCMDBServersTableName, strServiceNowInstanceName, strServiceNowUser, strServiceNowPassword)
strServiceNowQuery = "sys_class_name=cmdb_ci_win_server^ORsys_class_name=cmdb_ci_linux_server"
objServiceNowRESTQueryResponse = objServiceNowTableAPIClient.GetByQuery(strServiceNowQuery)
'this much does work and it does return a result set
'this is my attempt to convert this response into a list of ServerRecords, but this does not work currently:
lstSNServersList = JsonConvert.DeserializeObject(Of List(Of ServerRecord))(objServiceNowRESTQueryResponse.RawJSON)
The objServiceNowRestQueryResponse.RawJSON string looks like this (though much longer):
{
"result":[
{
"sys_id":"00040665dbxxxxxx96191e",
"u_recovery_time_achievable":"720",
"sys_class_name":"cmdb_ci_linux_server",
"host_name":"rlserver001"
},
{
"sys_id":"00ec543d1xxxx66e4bcb6d",
"u_recovery_time_achievable":"4",
"sys_class_name":"cmdb_ci_linux_server",
"host_name":"plserver001"
},
{
"sys_id":"0105d975dbxxxxx8961998",
"u_recovery_time_achievable":"",
"sys_class_name":"cmdb_ci_linux_server",
"host_name":"tlserver001"
}
]
}
This is the error message I get when trying to run my code:
Exception thrown: 'Newtonsoft.Json.JsonSerializationException' in
Newtonsoft.Json.dll
Additional information: Cannot deserialize the current JSON object
(e.g. {"name":"value"}) into type
'System.Collections.Generic.List`1[CMDBReconciliation.CMDBReconciliation+ServiceNowServerRecord]'
because the type requires a JSON array (e.g. [1,2,3]) to deserialize
correctly.
To fix this error either change the JSON to a JSON array (e.g.
[1,2,3]) or change the deserialized type so that it is a normal .NET
type (e.g. not a primitive type like integer, not a collection type
like an array or List) that can be deserialized from a JSON object.
JsonObjectAttribute can also be added to the type to force it to
deserialize from a JSON object.
Path 'result', line 1, position 10.
This will work. It just extracts the array part from the json string.
Dim start As Integer = objServiceNowRESTQueryResponse.IndexOf("[")
Dim last As Integer = objServiceNowRESTQueryResponse.LastIndexOf("]")
lstSNServersList = JsonConvert.DeserializeObject(Of List(Of ServerRecord))(objServiceNowRESTQueryResponse.Substring(start, last - start + 1))

Json conversion to double and bind to WPF nummericupdown fails

Currently i get an error on a binding. The situation is that i write my settings to a JSON file. When the app opens again the JSON file is read and used throughout the application. Now there's a strange thing: When i bind a double value to the value of a nummericupdown than i get an error: type 'JValue' to type 'System.Nullable1[System.Double]'for 'en-US' however this error doesn't occur when i recreate the JSON list and file. (simply said when i delete the file and restart the app it will create a new instance of a class en write it to disk)
Property in class:
Public Property SomeValue As Double
Write/Reader JSON:
'Write
Using _file As StreamWriter = New StreamWriter(SettingFilePath)
Dim serializer As New JsonSerializer()
serializer.Formatting = Formatting.Indented
serializer.Serialize(_file, Me)
End Using
'Read
Return JsonConvert.DeserializeObject(Of Settings)(File.ReadAllText(settingsfile))
JSON string:
"SomeValue": 1.0,
Binding in XAML:
<Controls:NumericUpDown
Width="200"
HorizontalAlignment="Center"
Maximum="5"
Minimum="1"
NumericInputMode="All"
Speedup="false"
Value="{Binding SomeValue}" />
Please note that i use the Mathapps Metro nummericupdown control version 1.6.5
Newtonsoft version 10.0.0.1 (Cannot update due to dependencies)
Edit:
As asked i digged deeper and now know where it starts, but don't know yet how to resolve it. Is start with my class for example:
Public class Hello
Dim a as Object
Dim b as EnumTypeOfObjectIn_A
Dim SomeOtherStuff as String
End class
Now when i DeserializeObject de file to the Class Hello then variable a becomes a object of type JObject and this is why alot of logica afterwords goes wrong. When i create the object in code everything goes well because the TypeOf object matches the one i put in. So is there a work arround for the Deserializer to convert the object to the one that is indicated in variable b ?
Found the solution i was looking for. The Newtonsoft JSON contains the JsonSerializerSettings class that helps the de/serialization process. For me it is important to add the Type of object when serializing so that's exactly what TypeNameHandling and TypeNameAssemblyFormatHandling is for in the Newtonsoft JSON assembly
I ended up with this code:
Public Class Hello
Public Property A As Object
Dim settingsfile As String = "C:\jsontest.json"
Public Sub Save()
Using _file As StreamWriter = New StreamWriter(settingsfile)
_file.Write(JsonConvert.SerializeObject(Me, Formatting.Indented, New JsonSerializerSettings() With {
.TypeNameHandling = TypeNameHandling.Objects,
.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
}))
End Using
End Sub
Public Function Load() As Hello
Return JsonConvert.DeserializeObject(Of Hello)(File.ReadAllText(settingsfile), New JsonSerializerSettings() With {.TypeNameHandling = TypeNameHandling.Objects})
End Function
End Class
Public Class Person
Public Property Name As String
Public Property Age As Integer
Sub New()
Me.Name = "John"
Me.Age = 130
End Sub
End Class
Producing this JSON output, note the $Type
{
"$type": "MyNamespace.Hello, MyNamespace",
"A": {
"$type": "MyNamespace.Person, MyNamespace",
"Name": "John",
"Age": 130
}
}

ArgumentOutOfRange exception when deserializing to a DataSet with Json.Net

I have the following JSON string in a variable called strJSON.
{
"results":[
{
"templateName":"HUD Section 8",
"userID":"2",
"mobileObjectId":"4582",
"source":"M",
"inspectionType":"A",
"notes":"Window in bedroom needs repair.",
"agencyID":"",
"requestDate":"2014-05-09 00:00:00",
"agencyName":"",
"inspectionTimeBegun":"2014-05-09 14:00:17",
"inspectionDate":"2014-05-09 14:30:00",
"inspectionID":135,
"inspectionTimeComplete":"2014-05-09 14:29:25",
"summaryDecision":"F",
"createdAt":"2014-05-09T18:29:35.050Z",
"updatedAt":"2014-05-09T18:29:35.050Z",
"objectId":"1FgtD6WT8Y",
"ACL":{
"*":{
"read":true
},
"cryZoU5gXJ":{
"write":true,
"read":true
}
}
}
]
}
When I call the following line of code...
ds = Newtonsoft.Json.JsonConvert.DeserializeObject(Of DataSet)(strJSON)
I get an exception with the message stating "Specified argument was out of the range of valid values"
The JSON string is created with the following REST API call to Parse.com.
strJSON = http.QuickGetStr(strURL)
I am using this elsewhere with success albeit with simpler Parse classes but I have gone through this JSON string carefully and can't see anything wrong.
Any ideas on what might be causing this error?
In order for Json.Net to deserialize into a DataSet, the JSON must be in a specific format, as described in this answer. Your JSON is close, but the problem is the ACL object. The DataTableConverter that Json.Net 5.0 uses expects all of the columns in the table to be simple data types or it will throw an ArgumentOutOfRangeException (source). Json.Net 6.0 supports nested data tables and arrays in addition to simple types, but your ACL data still does not meet the required format that would allow it to be deserialized correctly to a DataSet. You have a few different options for dealing with this:
Change the JSON
If you control the format of the JSON (i.e. it is not from a third party) you can change it such that Json.Net 6.0 will be able to deserialize it to a DataSet. Here is what it would need to look like for that to work:
{
"results": [
{
"templateName": "HUD Section 8",
"userID": "2",
"mobileObjectId": "4582",
"source": "M",
"inspectionType": "A",
"notes": "Window in bedroom needs repair.",
"agencyID": "",
"requestDate": "2014-05-09 00:00:00",
"agencyName": "",
"inspectionTimeBegun": "2014-05-09 14:00:17",
"inspectionDate": "2014-05-09 14:30:00",
"inspectionID": 135,
"inspectionTimeComplete": "2014-05-09 14:29:25",
"summaryDecision": "F",
"createdAt": "2014-05-09T18:29:35.050Z",
"updatedAt": "2014-05-09T18:29:35.050Z",
"objectId": "1FgtD6WT8Y",
"ACL": [
{
"user": "*",
"read": true,
"write": false
},
{
"user": "cryZoU5gXJ",
"read": true,
"write": true
}
]
}
]
}
With this format, the ACL column of the results table will contain a nested DataTable with the individual ACL rows, each row having three columns, user, read and write.
Deserialize to strongly-typed classes
Instead of deserializing into a DataSet, you could deserialize into a set of strongly-typed classes. The advantage to this approach is that everything is in an easily usable form. The disadvantage is that you need to know what is in the JSON before you can create the classes.
You can use third-party tools like json2csharp.com to help generate the classes from a sample of the JSON, as was suggested in another answer (now deleted), but note that this is not foolproof (and it doesn't do VB). Sometimes you will need to intervene and edit the classes manually. For example, if generate classes from the JSON in your question, you'll notice that it creates a fixed class for each ACL instance. This will not work unless your set of ACLs always has exactly two items, one called Everyone and the other CryZoU5gXJ. I think it is much more likely that the set of ACLs will be variable, so it makes sense to use a Dictionary for these. Here are the classes I would propose:
Class RootObject
Public Property results As List(Of Result)
End Class
Class Result
Public Property templateName As String
Public Property userID As String
Public Property mobileObjectId As String
Public Property source As String
Public Property inspectionType As String
Public Property notes As String
Public Property agencyID As String
Public Property requestDate As String
Public Property agencyName As String
Public Property inspectionTimeBegun As String
Public Property inspectionDate As String
Public Property inspectionID As Integer
Public Property inspectionTimeComplete As String
Public Property summaryDecision As String
Public Property createdAt As String
Public Property updatedAt As String
Public Property objectId As String
Public Property ACL As Dictionary(Of String, ACL)
End Class
Class ACL
Public Property read As Boolean
Public Property write As Boolean
End Class
With this class structure in place, you can deserialize like this:
Dim root As RootObject = JsonConvert.DeserializeObject(Of RootObject)(strJSON)
For the ACLs, the key for each dictionary entry will be the user ID (or * as you have in your example). If you don't actually care about the ACLs, you can simply omit the ACL property from the Result class. By default Json.Net will skip properties that exist in the JSON but do not exist in the class.
Use the LINQ-to-JSON API to parse the JSON
With Json.Net there is always more than one way to skin the cat. Json.Net's LINQ-to-JSON API really shines when the JSON you are parsing is highly variable and/or you don't want to create classes for receiving the data. You can deserialize any valid JSON to a hierarchy of JToken objects and then pick them apart however you need. For example, if you just needed a few select pieces of information from each result, you could do this:
Dim token As JToken = JToken.Parse(json)
For Each result As JObject In token("results").Children(Of JObject)()
Console.WriteLine("userID: " + result("userID").ToString())
Console.WriteLine("templateName: " + result("templateName").ToString())
Console.WriteLine("inspectionID: " + result("inspectionID").ToString())
Console.WriteLine("inspectionType: " + result("inspectionType").ToString())
Console.WriteLine("inspectionDate: " + result("inspectionDate").ToString())
Console.WriteLine("summaryDecision: " + result("summaryDecision").ToString())
Console.WriteLine("notes: " + result("notes").ToString())
Next
You could use this same approach to manually build a DataSet from the JSON. Here is a generic function that will deserialize JSON into a DataSet but ignore any complex objects (e.g. the ACLs) instead of throwing an exception:
Function DeserializeToDataSet(json As String) As DataSet
Dim root As JObject = JObject.Parse(json)
Dim ds As DataSet = New DataSet()
For Each prop As JProperty In root.Properties
If prop.Value.Type = JTokenType.Array Then
Dim dt As DataTable = ds.Tables.Add(prop.Name)
For Each row As JObject In prop.Value.Children(Of JObject)()
Dim dr As DataRow = dt.NewRow
For Each col As JProperty In row.Properties
Dim colType As Type = GetColumnType(col.Value.Type)
If Not colType Is Nothing Then
Dim dc As DataColumn = dt.Columns(col.Name)
If dc Is Nothing Then
dc = dt.Columns.Add(col.Name, colType)
End If
dr(col.Name) = col.Value.ToObject(colType)
End If
Next
dt.Rows.Add(dr)
Next
End If
Next
Return ds
End Function
Function GetColumnType(tokenType As JTokenType) As Type
If tokenType = JTokenType.String Then Return GetType(String)
If tokenType = JTokenType.Integer Then Return GetType(Integer)
If tokenType = JTokenType.Date Then Return GetType(DateTime)
If tokenType = JTokenType.Boolean Then Return GetType(Boolean)
If tokenType = JTokenType.Float Then Return GetType(Double)
Return Nothing
End Function
Of course if you need the ACLs, you'll need to customize this method to get that data into a form that is consumable by your code. I'll leave that part to you.
Json.Net will only parse directly into a DataSet if it conforms to a certain standard. See this answer for the layout it needs.
However, you could deserialize to an XML document and use the DataSet object's ReadXml method load it for you. See this question for details on how to do this.
(HT to Brian Rogers for dataset structure details)