C# filtering json results from restclient - c#

I am completely new to C# and have zero background in the language.
The only programming language I am an expert in is SQL.
My situation:
I have a an API url from a website my company uses to monitor product levels in tanks.
The API returns a json list of the tanks (location, tank id, tank size, current level, etc.).
My goal is to create a windows form application with an input field.
The user types the name of the location, clicks a button, and sees the information for any tanks at that location.
What I have tried:
What I have done so far:
Again, I have zero knowledge of programming so do not be critical if I have done things in very unusual/inefficient ways.
I have managed to create a windows form that has a button and text box.
When the button is clicked, the text box is populated with all of the tank data from the API.
I can't figure out how to filter the data that has been returned.
My current code is below...
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using RestSharp;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using static APIform.Form1;
namespace APIform
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var client = new RestClient("https://{{my tank monitor website}}/admin/data_feed_configs/140.json");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("auth-token", "{{my token}}");
request.AddHeader("auth-email", "{{my authorization email}}");
request.AddHeader("Authorization", "{{my auth code}}");
request.AddHeader("Cookie", "{{cookies}}");
IRestResponse response = client.Execute(request);
var data = response.Content;
JArray jsonArray = JArray.Parse(data);
textBox1.Text = jsonArray.ToString();
}
}
}
I have added a model class TankClass that resembles the text returned from the request.
public class TankClass
{
public string location_name { get; set; }
public string owner_name { get; set; }
public string tank_id { get; set; }
public string tank_name { get; set; }
public string product_name { get; set; }
public int tank_size { get; set; }
public string sensor_value { get; set; }
public string reading_inches { get; set; }
public string reading_volume { get; set; }
public string available_capacity { get; set; }
public int fill_percentage { get; set; }
public string fill_status { get; set; }
public string alert_status { get; set; }
public int days_to_empty { get; set; }
public string battery_level { get; set; }
public string product_sku { get; set; }
public string reading_time { get; set; }
public string division { get; set; }
}
Everything I try to deserialize/filter the results does not seem to be working.
What do I need to add into the code to make this work?
I have a second text box (textbox2) on the form.
This is where the user will enter "Warehouse 1" for example.
When they then hit the button, I would like only tanks at Warehouse 1 to show in the textbox1 field.
With my current code, this is a sample of what shows in the textbox1 field when clicking the button:
[
{
"location_name": "Warehouse 1",
"owner_name": "ABC Oil, Inc.",
"tank_id": "W00813862",
"tank_name": "Dow - Desitherm (TEG) Tank #M-20-065",
"product_name": "Dow - Desitherm (TEG)",
"tank_size": 2005.0,
"sensor_value": "2.379",
"reading_inches": "29.6",
"reading_volume": "908.2",
"available_capacity": "1096.8",
"fill_percentage": 45,
"fill_status": "COULD",
"alert_status": "",
"days_to_empty": 124.4,
"battery_level": "6.1",
"product_sku": "",
"reading_time": "2020-09-16T10:55:35-04:00",
"division": "1024"
},
{
"location_name": "Warehouse 2",
"owner_name": "ABC Oil, Inc.",
"tank_id": "Z057101",
"tank_name": "OSI 84 - Diesel Exhaust Fluid (DEF)",
"product_name": "Diesel Exhaust Fluid (DEF)",
"tank_size": 8806.0,
"sensor_value": "2554.0 | 3263.0",
"reading_inches": "76.3",
"reading_volume": "4868.8",
"available_capacity": "3937.2",
"fill_percentage": 55,
"fill_status": "GOOD",
"alert_status": "",
"days_to_empty": 14.5,
"battery_level": "-",
"product_sku": "",
"reading_time": "2020-09-16T10:59:00-04:00",
"division": ""
},
.
.
.
]
My Program.cs tab:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using RestSharp;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Security.Principal;
using System.Text;
using System.IO;
using static APIform.Form1;
namespace APIform
{
public class TankClass
{
public string location_name { get; set; }
public string owner_name { get; set; }
public string tank_id { get; set; }
public string tank_name { get; set; }
public string product_name { get; set; }
public double tank_size { get; set; }
public string sensor_value { get; set; }
public string reading_inches { get; set; }
public string reading_volume { get; set; }
public string available_capacity { get; set; }
public int fill_percentage { get; set; }
public string fill_status { get; set; }
public string alert_status { get; set; }
public double days_to_empty { get; set; }
public string battery_level { get; set; }
public string product_sku { get; set; }
public DateTime reading_time { get; set; }
public string division { get; set; }
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}

In your class just change tank_size and days_to_empty to a double and reading_time to DateTime.
Then Deserialize into a List<TankClass>
var listOfTanks = Newtonsoft.Json.JsonConvert.DeserializeObject<List<TankClass>>(data);
Then with the form data, you can use Linq to filter the results:
var returnValues = listOfTanks.Where(x => x.location_name == TextBox1.Text);

Related

How can I actually use JSON in C#?

I have a Json file that was deserialized from a Json Api-Call, now I have to use this file as an object in the main program.
Here is a small section of it:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Api1
{
public class EcmSimpleField
{
public string value { get; set; }
public string displayName { get; set; }
public string internalName { get; set; }
public string dbName { get; set; }
public bool visible { get; set; }
public string type { get; set; }
}
public class BaseParameter
{
public string value { get; set; }
public string type { get; set; }
}
public class SystemField
{
public string value { get; set; }
public string type { get; set; }
}
How can I use this file as an object in the main program and work with it?
Create a class for the json like you shared above and use deserialise it using Newtonsoft.json dll or any other library.
var obj = JsonConvert.DeserializeObject<YourClass>(jsonString);
This thread will probably help you:
How can I parse JSON with C#?
If your JSON file has changing parameters then the parameters will need to be retrieved in arrays because the array index will always be the same even if the "parameter" changes.

C# JsonSerializer.Deserialize array

All,
Edit: Firstly thanks for everyone's help. Secondly I'm new to Stack Overflow so apologises if I've added this edit incorrectly.
Following the commments and replies I've updated my class structure to:
services class:
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace RTT_API
{
class services
{
public List<service> service = new List<service>();
public services()
{
}
}
}
Service class:
using System;
using System.Collections.Generic;
using System.Text;
namespace RTT_API
{
class service
{
public string atocCode{get; set;}
public service()
{
}
}
}
Unfortunately I'm still getting the same error. I think I still haven't quite matched my class structure to the JSON structure? Unfortunately I'm not sure where my mistake is. If it helps to highlight my mistake using a comparison then the following works:
Location class
using System;
using System.Collections.Generic;
using System.Text;
namespace RTT_API
{
class location
{
public string name { get; set; }
public string crs { get; set; }
public location()
{
}
}
}
Location deserilisation command and test output:
location locations = JsonSerializer.Deserialize<location>(channelResponse.RootElement.GetProperty("location").GetRawText());
MessageBox.Show(locations.crs);
Original question:
My JSON is as follows:
{
"location": {
"name": "Bournemouth",
"crs": "BMH",
"tiploc": "BOMO"
},
"filter": null,
"services": [
{
"locationDetail": {
"realtimeActivated": true,
"tiploc": "BOMO",
"crs": "BMH",
"description": "Bournemouth",
"wttBookedArrival": "011630",
"wttBookedDeparture": "011830",
"gbttBookedArrival": "0117",
"gbttBookedDeparture": "0118",
"origin": [
{
"tiploc": "WATRLMN",
"description": "London Waterloo",
"workingTime": "230500",
"publicTime": "2305"
}
],
"destination": [
{
"tiploc": "POOLE",
"description": "Poole",
"workingTime": "013000",
"publicTime": "0130"
}
],
"isCall": true,
"isPublicCall": true,
"realtimeArrival": "0114",
"realtimeArrivalActual": false,
"realtimeDeparture": "0118",
"realtimeDepartureActual": false,
"platform": "3",
"platformConfirmed": false,
"platformChanged": false,
"displayAs": "CALL"
},
"serviceUid": "W90091",
"runDate": "2013-06-11",
"trainIdentity": "1B77",
"runningIdentity": "1B77",
"atocCode": "SW",
"atocName": "South West Trains",
"serviceType": "train",
"isPassenger": true
}
]
}
My class structure is as follows:
servicelist class:
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace RTT_API
{
class servicelist
{
public List<services> service = new List<services>();
public servicelist()
{
}
}
}
services class:
using System;
using System.Collections.Generic;
using System.Text;
namespace RTT_API
{
class services
{
public int serviceUid;
public services()
{
}
}
}
For deserialisation I have tried:
services servicelist = JsonSerializer.Deserialize<services>(channelResponse.RootElement.GetProperty("services").GetRawText());
and
servicelist servicelist = JsonSerializer.Deserialize<servicelist>(channelResponse.RootElement.GetProperty("services").GetRawText());;
In both cases I get 'System.Text.Json.JsonException'
I think there is a mismatch betwee the class structure and the JSON but I can't work what the problem is? It's the first time I've tried to desarialise an array.
Thanks
using System;
using System.Collections.Generic;
using System.Text;
namespace RTT_API
{
class location
{
public string name { get; set; }
public string crs { get; set; }
public location()
{
}
}
}
You can generate exact C# classes according to your JSON using tools for exactly that purpose. I used https://json2csharp.com/ , another is https://jsonutils.com/ - these are web services and don't require installation on computer, another option is generating classes through Visual Studio (with Web Essentials installed), there you would use Edit - Paste special - paste JSON as class.
Once you have the valid classes (I pasted generated classes below) you can deserialize entire Root object and then access any part of it, including services part:
// jsonInputText holds entire JSON string you posted
Root root = JsonSerializer.Deserialize<Root>(jsonInputText);
List<Service> serviceList = root.services;
Generated classes:
public class Location
{
public string name { get; set; }
public string crs { get; set; }
public string tiploc { get; set; }
}
public class Origin
{
public string tiploc { get; set; }
public string description { get; set; }
public string workingTime { get; set; }
public string publicTime { get; set; }
}
public class Destination
{
public string tiploc { get; set; }
public string description { get; set; }
public string workingTime { get; set; }
public string publicTime { get; set; }
}
public class LocationDetail
{
public bool realtimeActivated { get; set; }
public string tiploc { get; set; }
public string crs { get; set; }
public string description { get; set; }
public string wttBookedArrival { get; set; }
public string wttBookedDeparture { get; set; }
public string gbttBookedArrival { get; set; }
public string gbttBookedDeparture { get; set; }
public List<Origin> origin { get; set; }
public List<Destination> destination { get; set; }
public bool isCall { get; set; }
public bool isPublicCall { get; set; }
public string realtimeArrival { get; set; }
public bool realtimeArrivalActual { get; set; }
public string realtimeDeparture { get; set; }
public bool realtimeDepartureActual { get; set; }
public string platform { get; set; }
public bool platformConfirmed { get; set; }
public bool platformChanged { get; set; }
public string displayAs { get; set; }
}
public class Service
{
public LocationDetail locationDetail { get; set; }
public string serviceUid { get; set; }
public string runDate { get; set; }
public string trainIdentity { get; set; }
public string runningIdentity { get; set; }
public string atocCode { get; set; }
public string atocName { get; set; }
public string serviceType { get; set; }
public bool isPassenger { get; set; }
}
public class Root
{
public Location location { get; set; }
public object filter { get; set; }
public List<Service> services { get; set; }
}
If you need to deserialize only just a part of your json then you can use the JObject and JToken helper classes for that.
var json = File.ReadAllText("Sample.json");
JObject topLevelObject = JObject.Parse(json);
JToken servicesToken = topLevelObject["services"];
var services = servicesToken.ToObject<List<Service>>();
The topLevelObject contains the whole json in a semi-parsed format.
You can use the indexer operator to retrieve an object / array by using one of the top level keys.
On a JToken you can call the ToObject<T> to deserialize the data into a custom data class.
In order to be able to parse your json I had to adjust the services type because the W90091 as serviceUid can't be parsed as int. So here is my Service class definition:
public class Service
{
public string ServiceUid;
}
One thing to note here is that casing does not matter in this case so please use CamelCasing in your domain models as you would normally do in C#.
Thanks for everyone's help.
Firstly I had to make a few changes to the class names as they didn't match the JSON. I also had to change the syntax of two commands which I've detailed below:
I changed the definition of the list of objects from:
public List<services> service = new List<services>();
to:
public List<service> destination { get; set; };
and deserilisation command from:
services servicelist = JsonSerializer.Deserialize<services>(channelResponse.RootElement.GetProperty("services").GetRawText());
to
var servicelist = JsonSerializer.Deserialize<List<service>>(channelResponse.RootElement.GetProperty("services").GetRawText());
The change from services to var might not be the best solution. I think it's the first change, and matching the class names to the JSON, that fundamentally fixed the issue.

XML data not being loaded into c# class

I am trying to load data from an XML file into a c# class but am not getting data being loaded in Notifications. The rest of the class (not shown) is correctly populated so I am assuming that my class definition is incorrect. Can anyone shed any light on this?
public partial class ISTimetables
{
[XmlElement]
public List<ISNotification> Notifications { get; set; }
}
[Serializable()]
public partial class ISNotification
{
public ISNotification()
{
On = new List<ISProcessStep>();
Notify = new List<ISNotify>();
}
[XmlElement]
public List<ISProcessStep> On { get; set; }
[XmElement]
public List<ISNotify> Notify { get; set; }
}
[Serializable()]
public partial class ISNotify
{
public string Email { get; set; }
public string SimpleEmail { get; set; }
public string SMS { get; set; }
}
[Serializable()]
public enum ISProcessStep
{
[XmlEnum("Calculated")]
Calculated,
[XmlEnum("Reported")]
Reported,
[XmlEnum("Customer Approved")]
CustomerApproved,
[XmlEnum("Rejected")]
Rejected
}
The data I am trying to load is as follows:
<Notifications>
<Notification>
<On>Calculated</On>
<On>Reported</On>
<Notify SimpleEmail="me#company.com"/>
<Notify Email="you#company.com"/>
<Notify SMS="0123456789"/>
</Notification>
<Notification>
<On>Customer Approved</On>
<Notify Email="him#company.com"/>
</Notification>
</Notifications>
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Xml.Serialization;
namespace ConsoleApplication21
{
class Program
{
const string FILEName = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlSerializer serializer = new XmlSerializer(typeof(ISTimetables));
XmlTextReader reader = new XmlTextReader(FILEName);
ISTimetables tables = (ISTimetables)serializer.Deserialize(reader);
}
}
[XmlRoot("Notifications")]
public partial class ISTimetables
{
[XmlElement("Notification")]
public List<ISNotification> Notifications { get; set; }
}
[XmlRoot("Notification")]
public partial class ISNotification
{
public ISNotification()
{
On = new List<ISProcessStep>();
Notify = new List<ISNotify>();
}
[XmlElement]
public List<ISProcessStep> On { get; set; }
[XmlElement]
public List<ISNotify> Notify { get; set; }
}
[Serializable()]
public partial class ISNotify
{
public string Email { get; set; }
public string SimpleEmail { get; set; }
public string SMS { get; set; }
}
[Serializable()]
public enum ISProcessStep
{
[XmlEnum("Calculated")]
Calculated,
[XmlEnum("Reported")]
Reported,
[XmlEnum("Customer Approved")]
CustomerApproved,
[XmlEnum("Rejected")]
Rejected
}
}

Error with Newtonsoft.Json: an item with the same key has already been added

I'm using Newtonsoft.Json with signalR to send data to client side. The following code takes data from database and is supposed to send it but it gives the exception:
an item with the same key has already been added
The code is:
using Makbin.Data;
using Microsoft.AspNet.SignalR;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Hosting;
namespace Makbin.Web.Hubs
{
public class LogBackgroundTicker : IRegisteredObject
{
private readonly MakbinRepository _repository;
private Timer logsTimer;
private IHubContext hub;
public LogBackgroundTicker()
{
_repository = new MakbinRepository();
HostingEnvironment.RegisterObject(this);
hub = GlobalHost.ConnectionManager.GetHubContext<LogHub>();
logsTimer = new Timer(OnLogsTimerElapsed, null,
TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(7));
}
private void OnLogsTimerElapsed(object sender)
{
var result = _repository.Syslog.Select(x => x).Take(300);
var finalResult = JsonConvert.SerializeObject(result, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
hub.Clients.All.broadcastMessage(DateTime.UtcNow.ToString(), finalResult);
}
public void Stop(bool immediate)
{
logsTimer.Dispose();
HostingEnvironment.UnregisterObject(this);
}
}
}
and this is Syslog class:
public class Syslog
{
public Guid LogId { get; set; }
public Guid DeviceId { get; set; }
public string Name { get; set; }
public string Facility { get; set; }
public string Severity { get; set; }
public string DevTime { get; set; }
public string Text { get; set; }
public DateTime Time { get; set; }
public virtual Peripherals Peripheral { get; set; }
}
how can I fix this exception?

C# WindowsForm GridView - How to add class into GridView?

public class PurchaseOrderItem
{
public Int64 PONumber { get; set; }
public string Description { get; set; }
public string UM { get; set; }
public int QTY { get; set; }
public decimal Cost { get; set; }
}
foreach (PurchaseOrderItem item in po.PurchaseOrderItems)
{
dgvPOItem.Rows.Add(item);
}
The Foreach above isn't working.
I can't use DataSource since i need to add a blank row after adding the data
So there will be a empty row where users can add values on gridview in the future.
can't you use a BindingList<PurchaseOrderItem> ?
this should allow you to add items to your collection from dgv control (using empty row)
Edit: I've created simple WinForm app,
only DGV control in the main form
Form1.cs code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
list.Add( new PurchaseOrderItem() {
PONumber = 1,
Description = "First item",
UM = "something",
QTY = 2341,
Cost = 0.99M
} );
dataGridView1.DataSource = list;
dataGridView1.RowsAdded += new DataGridViewRowsAddedEventHandler( dataGridView1_RowsAdded );
}
void dataGridView1_RowsAdded( object sender, DataGridViewRowsAddedEventArgs e ) {
object o = list; // added for breakpoint with variable viewing
// you can watch your list changing here, when you add new rows
}
BindingList<PurchaseOrderItem> list = new BindingList<PurchaseOrderItem>();
}
public class PurchaseOrderItem {
public Int64 PONumber { get; set; }
public string Description { get; set; }
public string UM { get; set; }
public int QTY { get; set; }
public decimal Cost { get; set; }
}
}

Categories