Two almost identical functions, one works, the other does not? - c#

Working on writing a C# app to talk to our ServiceNow API. For some reason I can successfully ignore the root token of the JSON file it returns for the user, but not for the Incident. JSON returns seem to be almost identical save for the fact that one is from the incident table and the other is from the sys_user table.
Tried a couple of different ways of using Newtonsoft in the process of getting to the ignoring of the root token. Which has led me to where this code is at now (albeit unrefined)
void incidentCreator(string incID)
{
var restRequest = new RestRequest(_incTableLink + "/" + incID);
restRequest.AddParameter("sysparm_display_value", "true");
restRequest.AddParameter("sysparm_fields", "assigned_to,number,sys_updated_on,sys_id,comments_and_work_notes");
string incidentJsonString = _restInteractions.querySnow(restRequest);
Incident incident = null;
try
{
incident = JObject.Parse(incidentJsonString).SelectToken("result[0]").ToObject<Incident>();
}
catch (Exception e)
{
Console.WriteLine(e);
}
_incidentList.Add(incident);
}
void userGetter(string userID)
{
var restRequest = new RestRequest(_userTableLink);
restRequest.AddParameter("sysparm_query", "sys_id=" + userID);
string userJsonString = _restInteractions.querySnow(restRequest);
User user = null;
try
{
user = JObject.Parse(userJsonString).SelectToken("result[0]").ToObject<User>();
}
catch (Exception e)
{
Console.WriteLine(e);
}
//User user = JsonConvert.DeserializeObject<User>(userJsonString);
_userList.Add(user);
}
JSON string for incident that doesn't work
"{
\"result\": {
\"number\": \"INC1215653\",
\"sys_id\": \"52764be80007fb00903d51fea4ade50f\",
\"comments_and_work_notes\": \"2019-08-01 10: 46: 19 - PERSON (Additional comments)\\nEmailed PERSON
admin of the site to check permissions\\n",
\"sys_updated_on\": \"2019-08-01 10: 46: 17\",
\"assigned_to\": \"Employee\"
}
}"
JSON string for user which does work
"{
\"result\": [
{
\"calendar_integration\": \"1\",
\"last_position_update\": \"\",
\"u_lmc_account_indicator\": \"\",
\"u_supply_chain\": \"false\",
}
]
}"
No matter what I do with the incidentCreator() JSON parsing, using jsonconvert or the implementation present in current shared code. I either get "Object reference not set to an instance of an object." or just an object with all it's variables being null.
Any ideas? I'm sorry for the stripped back user json, it is a huge string and i couldn't be bothered cleaning it of PII before posting.

The JPath expression in your incidentCreator is incorrect.
It should be:-
try { incident = JObject.Parse(incidentJsonString).SelectToken("result").ToObject<Incident>(); }
Also, It's possibly just a result of pasting the JSON into the question, but the "comments_and_work_notes" field of the incident JSON example isn't escaped properly; just check you're receiving valid JSON using a linter to be sure.

The second JSON contents an array. You can see the différences [ (array) and { (object) after the word result
To parse for the case user, try it
JArray a = JArray.Parse(json);
var listUser = new List<User>();
foreach (JObject o in a.Children<JObject>())
{
foreach (JProperty p in o.Properties())
{
string name = p.Name;
string value = (string)p.Value;
var user = new User();
user.Name = name;
......
}
}

The difference is that your JSON string for incident has the field result which stores an object and the JSON string for user has the field result which contains an array of one object
incident
{
result :
{
...
}
}
user
{
result :
[
{
...
}
]
}
so the query SelectToken("result[0]") will work for the user since the result field contains an array with an element at index 0, but it wont work for the incident since there the result field does not conatin an array.
But SelectToken("result").ToObject<Incident>() should give you your Incident object.

Related

How to deserailise JSON Object in C# Without knowing structure

Is it possible to take a JSON object in C# that I read from from another source and convert the contents to files. The problem is I don't know the structure of the incoming objects.
So far I've got this:
var response = await client.GetAsync("https://blahblah/creds" +
"?ApiKey=" + textBox_password.Text +
"&deviceId=" + deviceId +
"&modelNum=" + modelNum);
var res = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var resJson = JsonConvert.DeserializeObject(res);
this.textBox_Stream.AppendText("responseBody = " + resJson + "\r\n");
Which gives me:
responseBody = {
"statusCode": 200,
"body": {
"modelNum": "42",
"creds": "[{\"deviceid.txt\":\"4DA23E\",\"pac.txt\":\"580795498743\"}]",
"deviceId": "4DA23E"
}
}
What I want to do is create a folder called 4DA23E and place one file inside it for each entry in the creds object.
device.txt will contain 4DA23E
pac.tct will contain 580795498743
etc. I can't figure out a dynamic way to extract the stuff I need. Goes without saying, I am not a C# programmer! So, please be kind.
Don't use JsonConvert.DeserializeObject. That's best when you know the schema of the JSON object and you've made a corresponding C# model.
First, parse the outer response JSON
var res = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
JObject resJson = JObject.Parse(res);
Now pull out the value of the "creds" property and re-parse it.
var credsJsonRaw = resJson["body"]["creds"].Value<string>();
JArray credsJson = JArray.Parse(credsJsonRaw);
Now you can loop through each object in the array, and each property in each object.
foreach (JObject obj in credsJson)
{
foreach (JProperty prop in obj.Properties)
{
Console.WriteLine($"Name = {prop.Name}");
Console.WriteLine($"Value = {prop.Value.Value<string>()}");
}
}
For your example, this prints
Name = deviceid.txt
Value = 4DA23E
Name = pac.txt
Value = 580795498743
One way to deserialise the following json is in two stages. This is because 'creds' is a string.
{
"statusCode": 200,
"body": {
"modelNum": "42",
"creds": "[{\"deviceid.txt\":\"4DA23E\",\"pac.txt\":\"580795498743\"}]",
"deviceId": "4DA23E"
}
}
Here's an example of how to do it. If you're not .NET6 change 'record' to 'class' and if you don't use nullable reference types (on by default in .NET6) remove the appropriate question marks in property definitions.
using System.Text.Json;
using System.Text.Json.Serialization;
var json = File.ReadAllText("data.json");
var o = JsonSerializer.Deserialize<Outer>(json);
if(o?.Body?.Creds == null)
{
throw new Exception("Bad data");
}
var credList = JsonSerializer.Deserialize<List<Dictionary<string, string>>>(o.Body.Creds);
if(credList == null)
{
throw new Exception("Bad data");
}
foreach( var e in credList)
foreach( var kvp in e)
Console.WriteLine($"{kvp.Key} : {kvp.Value}");
record Outer {
[JsonPropertyName("staticCode")]
public int? StaticCode {get; set;}
[JsonPropertyName("body")]
public Body? Body {get; set;}
}
record Body {
[JsonPropertyName("creds")]
public string? Creds {get; set;}
}
This prints
deviceid.txt : 4DA23E
pac.txt : 580795498743

Unable to build an array from a class in Xamarin.Forms with C#

I'm working on a Xamarin.Forms project with C# to connect to an OPC server and read values. I'm able to read the values, but I'm having trouble collating them into a list or array. After I do, I'd like to convert the values to ASCII.
Below is the code that is passed;
var readRequest = new ReadRequest
{
// set the NodesToRead to an array of ReadValueIds.
NodesToRead = new[] {
// construct a ReadValueId from a NodeId and AttributeId.
new ReadValueId {
// you can parse the nodeId from a string.
// e.g. NodeId.Parse("ns=2;s=Demo.Static.Scalar.Double")
NodeId = NodeId.Parse("ns=2;s=Link_CatConHybrid.2D.InStr1"),
//NodeId.Parse(VariableIds.Server_ServerStatus),
// variable class nodes have a Value attribute.
AttributeId = AttributeIds.Value
},
new ReadValueId
{
NodeId = NodeId.Parse("ns=2;s=Link_CatConHybrid.2D.InStr2"),
AttributeId = AttributeIds.Value
}
}
};
// send the ReadRequest to the server.
var readResult = await channel.ReadAsync(readRequest);
// DataValue is a class containing value, timestamps and status code.
// the 'Results' array returns DataValues, one for every ReadValueId.
DataValue dvr = readResult.Results[0];
DataValue dvr2 = readResult.Results[1];
Console.WriteLine("The value of Instr1 is {0}, InStr2 is {1}", dvr.Variant.Value, dvr2.Variant.Value);
What am I doing wrong or overlooking?
Edit: How would I combine all of the readResults into one ?
Just create a DataValue list and store them. Try like:
List<DataValue> endResult = new List<DataValue>();
foreach (DataValue value in readResult.Results)
{
endResult.Add(value);
}
Since Results is alerady collection of DataValue, you can just say
var dataValueCollection = readResult.Results; // if you want return collection you can just say return readResult.Results
If you are trying to write the values to console, then you can have loop directly on readResult.Results as below:
foreach(var dv in readResult.Results)
{
Console.WriteLine("The Value of InStr = {0}", dv.Variant.Value);
Console.WriteLine("The Value of InStr = {0}", dv.Variant.ReadValueId); // This line shows how to access ReadValueId.
// You can access other properties same as above
}

Displaying the name of each object

I'm creating software where the users can create and load profiles to fill textboxes. The names and other information contained in the profile are stored in a JSON file. A profile name can contain any text that is entered by the user.
So for this, I'm trying to get each objects names of the JSON file (= each profile name) to display them in a treeview, but all I get is their contents.
I have a JSON file containing two objects:
[
{
"profile1": {
//Some informations 1
},
"profile2": {
//Some informations 2
}
}
]
For now, I have code that allows me to get the value of a given tag, but I can't find a way to get the name of each objects:
using (StreamReader r = File.OpenText(path))
{
string json = r.ReadToEnd();
dynamic array = JsonConvert.DeserializeObject(json);
foreach (var item in array)
{
debug_tb.Text += item.profile1; //Gives me each values of the "profile1 object"
}
}
So what I'm trying to get is to display "profile1" and "profile2" and "profile3" if it exists.
Your problem is that your JSON is an array with one object. So you can simplefy the JSON first:
{
"profile1": {
//Some informations 1
},
"profile2": {
//Some informations 2
}
}
Then you can easyly iterate over every item in the JSON and get the Name of it
dynamic array = JsonConvert.DeserializeObject("{ \"profile1\": { }, \"profile2\": { } }");
foreach (var item in array)
{
debug_tb.Text += item.Name; //Gives the name of the object
}
Console.WriteLine(text);
Console.ReadLine();

Getting Nested json element returns an error

I'm trying to a make an application that interacts with the randomuser.me API
but this always returns some kinda of error, this time i'm using a code that i've found in stackoverflow to parse the json content.
So here is my code rightnow:
public string GetJsonPropertyValue(string json, string query)
{
JToken token = JObject.Parse(json);
foreach (string queryComponent in query.Split('.'))
{
token = token[queryComponent];
}
return token.ToString();
}
string getName()
{
string name = "";
try
{
using (WebClient wc = new WebClient())
{
var json = wc.DownloadString("https://randomuser.me/api/");
name = GetJsonPropertyValue(json, "results[0].name.first");
return name;
}
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
return name;
}
}
I don't really know what's the exact problem but it's returning a System.NullReferenceException
EDIT
If i don't insert the index in the second parameter of the GetJsonPropretyValue group method, and insert it this way results.name.first
It returns such an error:
System.ArgumentException: Accessed JArray values with invalid key value: >"name". Array position index expected.
at Newtonsoft.Json.Linq.JArray.get_Item(Object key)
Trying to split the JSON path on dots like you are doing isn't going to work when you have an array index in the path. Fortunately, you don't have to roll your own query method; the built-in SelectToken() method does exactly what you want:
using (WebClient wc = new WebClient())
{
var json = wc.DownloadString("https://randomuser.me/api/");
JToken token = JToken.Parse(json);
string firstName = (string)token.SelectToken("results[0].name.first");
string lastName = (string)token.SelectToken("results[0].name.last");
string city = (string)token.SelectToken("results[0].location.city");
string username = (string)token.SelectToken("results[0].login.username");
...
}
Fiddle: https://dotnetfiddle.net/3kh0b0

What is the recommended way to respond to bad user input with a REST API?

Let's assume the following:
I have a rest API that will return me names of fruits, and there are only 5 fruits.
To get the fruit name, I have to request an ID.
Consider the following code:
public class Fruit {
public int FruitID { get; set; }
public string FruitName { get; set; }
public Fruit(string json){
JObject o = JObject.Parse(json);
FruitID = Int32.Parse((string) o["id"]);
FruitName = (string) o["name");
}
}
public static Fruit getFruit(int id){
Task<Fruit> task = "http://fruit.com/get_fruit"
.SetQueryParams(new { fruit_id = id })
.GetStringAsync();
return new Fruit(task.Result);
}
(If anything looks wrong at this point please correct me, I am new to C# Tasks)
Let's say when that Task returns, the json could look like the following if it received a valid ID...
{
"status":1,
"id": 3,
"name": "apple"
}
Or this if it received an invalid ID.
{
"status":0
}
If the user is supposed to enter which ID is searched for, then there is a chance they could enter an ID which does not exist, since there are only 5, (0 through 4). Based on the code I entered above, I can see the application crashing if a "status":0 is returned, as it would not have the two fields the class constructor is looking for.
My question is: What is the best way to handle possible invalid inputs (such as the user entering ID of 20)?
The recommended way for a RESTful API is to use HTTP Error codes, in your case it would be 404 (Not found), since the fruit requested does not exist.
You should handle the error codes before trying to create the object. So check whether the request has been successfully executed (200 OK), and then process the payload.
Here's a reference of status codes:
http://www.restapitutorial.com/httpstatuscodes.html
input validation is one of the important tasks in web service development. i personally have two phase. at first i check the object for null values. i wrote this method in order to do it:
private bool HasNull(object webServiceInput, string[] optionalParameters = null)
{
if (ReferenceEquals(null, webServiceInput))
return false;
if (optionalParameters == null)
optionalParameters = new string[0];
var binding = BindingFlags.Instance | BindingFlags.Public;
var properties = webServiceInput.GetType().GetProperties(binding);
foreach (var property in properties)
{
if (!property.CanRead)
continue;
if (property.PropertyType.IsValueType)
continue;
if (optionalParameters.Contains(property.Name))
continue;
var value = property.GetValue(webServiceInput);
if (ReferenceEquals(null, value))
return false;
}
return true;
}
then if some of the inputs should have specified validation i check it individually. for example i check the ID be between 0 and 5;
i hope it could help you.

Categories

Resources