How to iterate through all keys of json node - json

I'm trying to scrap the key values from this website API and it seems the json format it's not an array.
I'm working with console .Net core 6.0 using System.Text.Json.Nodes
The code I'm using is :
Dim streamData As Stream = Nothing
Using http As HttpClient = New HttpClient
Dim url As String = "https://api.hotbit.io/api/v1/market.status24h"
Dim t As Task(Of Stream) = http.GetStreamAsync(url)
streamData = t.Result
End Using
Dim jsonResponse As JsonNode = JsonNode.Parse(streamData)
Dim jsonData As JsonNode = jsonResponse("result")
Dim c As String = String.Empty
For Each jsonCurrency As JsonNode In jsonData.AsObject
c += jsonCurrency("last").ToString + " "
Next
but I get the error:
Cannot convert type 'KeyValuePair(Of String, JsonNode)' in JsonNode
What Am I doing wrong?
Thanks

Create a class to represent your JSON, like this:
Public Class MarketStatus
Public Property IsChange As Boolean
Public Property period As Integer
Public Property open As String
Public Property last As String
Public Property high As String
Public Property low As String
Public Property volume As String
Public Property deal As String
Public Property close As String
Public Property base_volume As String
Public Property quote_volume As String
End Class
Public Class Payload
Public Property _error As Object
Public Property result As Result
Public Property id As Integer
End Class
Public Class Result
<JsonPropertyName("0xBTCBTC")>
Public Property _0xBTCBTC As MarketStatus
<JsonPropertyName("0xBTCETH")>
Public Property _0xBTCETH As MarketStatus
<JsonPropertyName("0xCASHUSDT")>
Public Property _0xCASHUSDT As MarketStatus
<JsonPropertyName("1INCH1D3LUSDT")>
Public Property _1INCH1D3LUSDT As MarketStatus
' etc...
End Class
Now you can deserialize the entire payload by using JsonSerializer.Deserialize or JsonSerializer.DeserializeAsync:
Dim payloadObject = Await JsonSerializer.DeserializeAsync(Of Payload)(streamData)
Update
Per our conversation in the comments of this answer, you want to get the last value of each MarketStatus without having to type each one manually. What you can do is:
Use reflection to get every property of the Result class
Loop over the collection
Use PropertyInfo.GetValue to get the value of the deserialized object
Here is an example using the same variable names as above:
For Each propertyInformation In GetType(Result).GetProperties()
Dim status = DirectCast(propertyInformation.GetValue(payloadObject.result), MarketStatus)
Console.WriteLine("{0}.last = {1}", propertyInformation.Name, status.last)
Next
Fiddle: https://dotnetfiddle.net/USaAgc

I solved using
Dim result As JsonObject = jsonResponse("result").AsObject
For Each kvp In result.AsEnumerable
c &= kvp.Value("last").ToString & ", "
Next

Related

Getting info from JSON in VB.NET using Json.NET

I need to process this JSON:
{
"companies": [
{
"companyId": "S86jhs89F",
"companyName": "LoremIpsum"
}
],
"response_metadata": {
"next_cursor": 659,
"next_link": "somedata"
} }
How can I get companyId, companyName, next_cursor and next_link using VB.NET?
Update...
I found this...
Dim json As String = "{ ... textJSON ... }"
Dim ser As JObject = JObject.Parse(json)
Dim data As List(Of JToken) = ser.Children().ToList
Dim output As String = ""
For Each grupo As JProperty In data
grupo.CreateReader()
Select Case grupo.Name
Case "companies"
For Each item As JObject In grupo.Values
output += vbCrLf + " -- " + item("companyId").ToString
output += vbCrLf + " -- " + item("companyName").ToString
Next
Case "response_metadata"
Dim dato As JObject = grupo.Value
output += vbCrLf + " -- " + dato("next_cursor").ToString
End Select
Next
I don´t know if this is the optimal way, but it is working...
Visual Studio has a cool feature called Paste JSON as Classes that can be found under Edit > Paste Special > Paste JSON as Classes. If you were to do this, then you would get something that looks like the following:
Public Class Rootobject
Public Property companies() As Company
Public Property response_metadata As Response_Metadata
End Class
Public Class Response_Metadata
Public Property next_cursor As Integer
Public Property next_link As String
End Class
Public Class Company
Public Property companyId As String
Public Property companyName As String
End Class
You can use decorators to make the property names conform to a more .NET style if you wanted:
Public Class Rootobject
<JsonProperty("companies")>
Public Property Companies As IEnumerable(Of Company)
<JsonProperty("response_metadata")>
Public Property ResponseMetadata As Response_Metadata
End Class
Public Class Response_Metadata
<JsonProperty("next_cursor")>
Public Property NextCursor As Integer
<JsonProperty("next_link")>
Public Property NextLink As String
End Class
Public Class Company
<JsonProperty("companyId")>
Public Property CompanyId As String
<JsonProperty("companyName")>
Public Property CompanyName As String
End Class
Now you would use JsonConvert.DeserializeObject to convert the JSON literal to your root object. After that, it's just a matter of getting the required properties:
Dim conversion = JsonConvert.DeserializeObject(Of Rootobject)(literal)
Dim companyId = conversion.Companies.First().CompanyId
Dim companyName = conversion.Companies.First().CompanyName
' etc.
Example: https://dotnetfiddle.net/GiGKwI

How to deserialize nested JSON arrays with Json.Net?

I want to deserialize the response of a HTTP-Request to Objects.
The Response looks like this:
[
"BeginOfEnumerable",
[
{
"StatusID": 12345,
"ItemID": 987654
}
],
"EndOfEnumerable"
]
I am using Newtonsoft.Json.
I tried to use Visual Studio's Edit > Paste special > Paste JSON as Classes to create the class model, but the result looks strange to me:
Public Class Rootobject
Public Property Property1() As Object
End Class
Also, this throws an Exception:
Dim myObject As Rootobject = JsonConvert.DeserializeObject(response.Content)
» Unexpected character encountered while parsing value: .Path ", line
0, position 0.
I want to get the StatusID and ItemID.
A JSON Validator I used says this JSON is valid.
The JSON structure is valid, per se, you may have some difficulty with the strings that don't follow the name:value pattern.
You could deserialize only the inner array of values, as a List(class), e.g.,
Imports System.Collections.Generic
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Public Class EnumObject
Public Property StatusId As Long
Public Property ItemId As Long
End Class
'[...]
Dim jsonEnumsTokens = JArray.Parse(response.Content)
Dim enumsArray = jsonEnumsTokens.Children.Skip(1).First().ToObject(Of List(Of EnumObject))()
which returns the list of EnumObject that contain the values, if that's all you're interested in and you don't need to handle this JSON in any other way, or serialize it back in the same form.
If you want to get the JSON representation of the arrays only:
Dim justTheArraysJson = jsonEnumsTokens.Children.Skip(1).First().ToString()
In case you want to perform deserialization and serialization, maintaining the structure, you could use a custom JsonConverter that handles this case.
The EnumObjectsConverter creates an intermediate structure that contains both the single strings and the array of values, contained in the EnumObjectArray property.
This also allows to serialize back to the original structure, if needed.
Call it as:
Dim enumArray = EnumObjectsHandler.Deserialize(response.Content)
Dim serialized = EnumObjectsHandler.Serialize(enumArray)
The EnumObjectsHandler class that provides the static methods for the serialization:
Public Class EnumObjectsHandler
Private Shared settings As JsonSerializerSettings = New JsonSerializerSettings() With {
.Converters = {New EnumObjectsConverter()}
}
Public Shared Function Deserialize(json As String) As List(Of EnumObjectsRoot)
Return JsonConvert.DeserializeObject(Of List(Of EnumObjectsRoot))(json, settings)
End Function
Public Shared Function Serialize(data As List(Of EnumObjectsRoot)) As String
Return JsonConvert.SerializeObject(data, settings)
End Function
Public Class EnumObject
Public Property StatusId As Long
Public Property ItemId As Long
End Class
Public Structure EnumObjectsRoot
Public Property EnumObjectArray As List(Of EnumObject)
Public Property Operation As String
Public Shared Widening Operator CType(objectArray As List(Of EnumObject)) As EnumObjectsRoot
Return New EnumObjectsRoot With {.EnumObjectArray = objectArray}
End Operator
Public Shared Widening Operator CType(op As String) As EnumObjectsRoot
Return New EnumObjectsRoot With {.Operation = op}
End Operator
End Structure
Friend Class EnumObjectsConverter
Inherits JsonConverter
Public Overrides Function CanConvert(t As Type) As Boolean
Return t Is GetType(EnumObjectsRoot)
End Function
Public Overrides Function ReadJson(reader As JsonReader, t As Type, existingValue As Object, serializer As JsonSerializer) As Object
Select Case reader.TokenType
Case JsonToken.String
Dim stringValue = serializer.Deserialize(Of String)(reader)
Return New EnumObjectsRoot() With {
.Operation = stringValue
}
Case JsonToken.StartArray
Dim arrayValue = serializer.Deserialize(Of List(Of EnumObject))(reader)
Return New EnumObjectsRoot() With {
.EnumObjectArray = arrayValue
}
End Select
Throw New Exception("EnumObjectsRoot could not be deserialized")
End Function
Public Overrides Sub WriteJson(writer As JsonWriter, untypedValue As Object, serializer As JsonSerializer)
Dim value = CType(untypedValue, EnumObjectsRoot)
If value.Operation IsNot Nothing Then
serializer.Serialize(writer, value.Operation)
Return
End If
If value.EnumObjectArray IsNot Nothing Then
serializer.Serialize(writer, value.EnumObjectArray)
Return
End If
Throw New Exception("EnumObjectsRoot could not be serialized")
End Sub
End Class
End Class

Read REST API JSON reply

I have been searching the web back an forth but couldn't find a hint to my issue.
I'm calling a REST API via RestSharp Client. I retrieve a response like this:
{
"meta": {
"query_time": 0.007360045,
"pagination": {
"offset": 1,
"limit": 100,
"total": 1
},
"powered_by": "device-api",
"trace_id": "a0d33897-5f6e-4799-bda9-c7a9b5368db7"
},
"resources": [
"1363bd6422274abe84826dabf20cb6cd"
],
"errors": []
}
I want to query the value of resources at the moment. This is the code I use:
Dim id_request = New RestRequest("/devices/queries/devices/v1?filter=" + filter, Method.GET)
id_request.AddHeader("Accept", "application/json")
id_request.AddHeader("Authorization", "bearer " + bearer)
Dim data_response = data_client.Execute(id_request)
Dim data_response_raw As String = data_response.Content
Dim raw_id As JObject = JObject.Parse(data_response_raw)
Dim id = raw_id.GetValue("resources").ToString
Unfortunately, I'm only getting ["1363bd6422274abe84826dabf20cb6cd"] as a reply instead of 1363bd6422274abe84826dabf20cb6cd
Can anyone point me into the right direction?
I have also tried to deserialize using JsonConvert.DeserializeObject() but I somehow fail.
I found this solution here but if I try to rebuild it fails as it doesn't recognize the Dictionary part
Dim tokenJson = JsonConvert.SerializeObject(tokenJsonString)
Dim jsonResult = JsonConvert.DeserializeObject(Of Dictionary(Of String, Object))(jsonString)
Dim firstItem = jsonResult.Item("data").Item(0)
EDIT:
When trying to deserialize the root as suggested but seems as if the 2nd response is nested JSON.
I have a reply like:
dr = {
"meta": {
"query_time": 0.004813129,
"powered_by": "device-api",
"trace_id": "5a355c86-37f7-416d-96c4-0c8796c940fc"
},
"resources": [
{
"device_id": "1363bd6422274abe84826dabf20cb6cd",
"policies": [
{
"policy_type": "prevention",
"policy_id": "1d34205a4e2c4d1991431c037c8e5734",
"applied": true,
"settings_hash": "7cb00a74",
"assigned_date": "2021-02-22T13:56:37.759459481Z",
"applied_date": "2021-02-22T13:57:19.962692301Z",
"rule_groups": []
}
],
"meta": {
"version": "352"
}
}
],
"errors": []
}
and I tried:
Dim restApiResponse = JsonConvert.DeserializeObject(Of RestApiResponseRoot)(dr)
' This is your array of strings
Dim resources = restApiResponse.Resources
Unfortunately I get
Newtonsoft.Json.JsonReaderException: 'Unexpected character encountered while parsing value: {. Path 'resources', line 8, position 3.'
The resources Property is an array. As usual, you need to specify which element of the array you want to consider. In this case, the first one, i.e., the element at index 0.
Dim jsonObject = JObject.Parse(data_response_raw)
Dim firstResource = jsonObject("resources")(0).ToString()
If you instead want the array content as a String array, not just the first element - assuming resources could contain more than one string (it's an array after all) - deserialize to String():
Dim jsonObject = JObject.Parse(data_response_raw)
Dim resources = JsonConvert.DeserializeObject(Of String())(jsonObject("resources").ToString())
In case you need the whole JSON response, I suggest to deserialize to a class Model that represents the JSON:
Public Class RestApiResponseRoot
Public Property Meta As Meta
Public Property Resources As List(Of String)
Public Property Errors As List(Of Object)
End Class
Public Class Meta
<JsonProperty("query_time")>
Public Property QueryTime As Double
Public Property Pagination As Pagination
<JsonProperty("powered_by")>
Public Property PoweredBy As String
<JsonProperty("trace_id")>
Public Property TraceId As Guid
End Class
Public Class Pagination
Public Property Offset As Long
Public Property Limit As Long
Public Property Total As Long
End Class
You can then deserialize the Model's Root object - the class named RestApiResponseRoot here - and access its Properties as usual:
Dim restApiResponse = JsonConvert.DeserializeObject(Of RestApiResponseRoot)(
data_response_raw
)
' This is your array of strings
Dim resources = restApiResponse.Resources
The other JSON response is slightly different, the Response Property contains an array of objects instead of string.
Some more properties and nested object are added. You just need to adjust the Model.
Public Class RestApiResponseRoot2
Public Property Meta As RootObjectMeta
Public Property Resources As List(Of Resource)
Public Property Errors As List(Of Object)
End Class
Public Class RootObjectMeta
<JsonProperty("query_time")>
Public Property QueryTime As Double
<JsonProperty("powered_by")>
Public Property PoweredBy As String
<JsonProperty("trace_id")>
Public Property TraceId As Guid
End Class
Public Class Resource
<JsonProperty("device_id")>
Public Property DeviceId As String
Public Property Policies As List(Of Policy)
Public Property Meta As ResourceMeta
End Class
Public Class ResourceMeta
Public Property Version As String
End Class
Public Class Policy
<JsonProperty("policy_type")>
Public Property PolicyType As String
<JsonProperty("policy_id")>
Public Property PolicyId As String
Public Property Applied As Boolean
<JsonProperty("settings_hash")>
Public Property SettingsHash As String
<JsonProperty("assigned_date")>
Public Property AssignedDate As DateTimeOffset
<JsonProperty("applied_date")>
Public Property AppliedDate As DateTimeOffset
<JsonProperty("rule_groups")>
Public Property RuleGroups As List(Of Object)
End Class
Dim restApiResponse2 = JsonConvert.DeserializeObject(Of RestApiResponseRoot2)(dr)
Dim resources As List(Of Resource) = restApiResponse2.Resources
' DeviceId of the first Resources object
Dim deviceId = resources(0).DeviceId
You can use some on-line resources to handle your JSON objects:
JSON Formatter & Validator
QuickType - JSON to .Net classes - C#, no VB.Net
JSON Utils - JSON to .Net classes - includes VB.Net. Somewhat less capable than QuickType.
Try trimming the resources value with " character in first and last of the output

Parse Dynamic Json Object into VB Classes

I have this nested structure
and want to parse it into classes.
I have this code to get the json file and to deserialize it
Public Function getData(ByVal _token As String, ByVal _identifier As String) As Results_FullData
Dim client = New RestClient(_baseURI)
Dim request = New RestRequest("/datasource/{id}/data", Method.GET)
request.AddParameter("id", _identifier)
request.AddUrlSegment("id", _identifier)
request.AddHeader("Authorization", "Bearer " + _token)
request.AddHeader("environment", _environment)
Dim jstr = client.Execute(request).Content
Dim allData As Results_FullData = JsonConvert.DeserializeObject(Of Results_FullData)(jstr)
Return allDATA
End Function
And build this class structure
Public Class Results_FullData
Public Property results As List(Of DSContent)
End Class
Public Class DSContent
Public Property userRunId As Long
Public Property metaColumnValues As List(Of String)
Public Property dataColumnValues As List(Of String)
End Class
But running the code the object datasourceInfo is empty and I do not know why. I thought I could just adopt the solution of this answer but it does not work. I guess the List(Of String) part is wrong. The problem mibht be that the length of metaColumnValues und dataColumnValues differs within each object {}. The idea is to get it into a string and seperate it later, since the values are , seperated within the object
Anyone who can help me here?
Edit:
Dataexample:
{"result":[{"userRunId":"xxxxxxx","metaColumnValues":["9006409","20073"],"dataColumnValues":["","superior"]},{"userRunId":"xxxxxxx","metaColumnValues":["2345","235","1"],"dataColumnValues":["","superior", "test"]}]}
In Results_FullData, the property is called results, but in the example JSON, it's called result. Also, DSContent.userRunId is declared as a Long, even though in the JSON, that property contains String values. If you fix those two things in your data classes, it properly deserializes your example data:
Public Sub Main()
Dim json As String = "{""result"":[{""userRunId"":""xxxxxxx"",""metaColumnValues"":[""9006409"",""20073""],""dataColumnValues"":["""",""superior""]},{""userRunId"":""xxxxxxx"",""metaColumnValues"":[""2345"",""235"",""1""],""dataColumnValues"":["""",""superior"", ""test""]}]}"
Dim allData As Results_FullData = JsonConvert.DeserializeObject(Of Results_FullData)(json)
End Sub
Public Class Results_FullData
Public Property result As List(Of DSContent)
End Class
Public Class DSContent
Public Property userRunId As String
Public Property metaColumnValues As List(Of String)
Public Property dataColumnValues As List(Of String)
End Class

Iterate Object Deserialized JSON

I cannot figure out how to iterate through an object to retrieve a list of errors sent in a JSON string. I have a For Each loop looing at each key pair value in my JSON but the third key pair is an object of key pairs. I cannot access these using the same For Each structure (I can access them if I loop through the object but want to keep it consistent if possible.. My 'Case Is "Errors" case statement is where I want to iterate the errors object but I am not sure how to get to it.. Here is my code.. I hope someone can assist..
Sample JSON:
{"success":"true","api_reference":3821,"errors":[{"record":"landlord","record_id":"-16::1::LPMB40-2385DDDC","error":"Please
ensure the email field has been
completed","error_code":"33101"},{"record":"landlord","record_id":"-16::1::LPMB40-2385DDDC","error":"Please
ensure the email field is a valid email
address","error_code":"33102"}]}
Dim jss = New JavaScriptSerializer()
Dim data = jss.Deserialize(Of Object)(responseFromServer)
Dim strSuccess = "", strAPIReference = ""
Dim intExpiresIn = 0
Dim ErrorsObject As Object
For Each kvp As KeyValuePair(Of String, Object) In data
Select Case kvp.Key
Case Is = "success"
strSuccess = kvp.Value
Case Is = "api_reference"
strAPIReference = kvp.Value
Case Is = "errors"
ErrorsObject = kvp.Value
For Each errorskvp As KeyValuePair(Of String, Object) In ErrorsObject
Next
End Select
Next
I would suggest doing this strongly typed, so that you can deserialize the json to a real object and use the object properties. That way it's much easier to read the values and loop errors etc:
Classes:
Public Class JsonResponse
Public Property Success As Boolean
Public Property Api_reference As String
Public Property Errors As IEnumerable(Of JsonError)
End Class
Public Class JsonError
Public Property Record As String
Public Property Record_Id As String
Public Property [Error] As String
Public Property Error_Code As String
End Class
Deserialization and use:
Dim j As New JavaScriptSerializer()
Dim data As JsonResponse = j.Deserialize(Of JsonResponse)(responseFromServer)
If Not data.Success Then
For Each myError As JsonError In data.Errors
Next
End If