I'm attempting to parse a rather convoluted/unnecessarily complicated JSON output using newtonsoft in C# however for some reason my parser always returns null and doesn't elaborate as to exactly why this is the case.
An example of a JSON file I'm trying to parse:
{
"response": {
"success": 1,
"current_time": 1362339098,
"prices": {
"35": {
"11": {
"0": {
"current": {
"currency": "keys",
"value": 39,
"value_high": 41,
"date": 1357515306
},
"previous": {
"currency": "keys",
"value": 37,
"value_high": 39
}
}
},
"3": {
"0": {
"current": {
"currency": "metal",
"value": 0.33,
"value_high": 0.66
}
}
}
},
"5002": {
"6": {
"0": {
"current": {
"currency": "usd",
"value": 0.39,
"value_high": 0.42,
"date": 1358090106
}
}
}
},
"5022": {
"6": {
"1": {
"current": {
"currency": "metal",
"value": 1.33,
"value_high": 1.55,
"date": 1357515175
}
}
}
}
}
}
}
And the C# parser I'm using. I run the getCurrentPrices() to return a PriceParser object but instead the object returned is always null.
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using System.Diagnostics;
namespace SteamBot
{
class PriceParser
{
//Methods
public PriceParser updatePrices()
{
var json = File.ReadAllText("test.json");
ParserResult result = JsonConvert.DeserializeObject<ParserResult>(json);
return result.result;
}
public Data currentPrices { get; set; }
//DATA
public class Data
{
public Response Response { get; set; }
}
public class Response
{
public string success { get; set; }
public string current_time {get; set;}
public List<Price> prices { get; set;}
}
public class Price
{
public int defindex { get; set; }
public int quality { get; set; }
public Current current { get; set; }
public Previous previous { get; set; }
}
public class Current
{
public string currency { get; set; }
public float value { get; set; }
public float value_high { get; set; }
public int date { get; set; }
}
public class Previous
{
public string currency { get; set; }
public float value { get; set; }
public float value_high { get; set; }
public int date { get; set; }
}
protected class ParserResult
{
public PriceParser result { get; set; }
}
}
}
I'm probably just missing something stupid but for the life of me I can't figure out what, anyone with more JSON wrangling experience know what's going on here?
You are getting null values because your class structure does not match your JSON.
The first issue is that you are deserializing into a ParserResult when you should be using a Data. Data has a response property, matching your JSON. ParserResult does not have this property.
The second issue is that you have defined prices to be a List<Price>, but your JSON does not contain an array. Instead, the JSON structure is actually a series of nested dictionaries.
Try defining your inner classes like this:
public class Data
{
public Response response { get; set; }
}
public class Response
{
public int success { get; set; }
public long current_time { get; set; }
public IDictionary<int, IDictionary<int, IDictionary<int, Price>>> prices { get; set; }
}
public class Price
{
public Quote current { get; set; }
public Quote previous { get; set; }
}
public class Quote
{
public string currency { get; set; }
public decimal value { get; set; }
public decimal value_high { get; set; }
public long date { get; set; }
}
Then, in your updatePrices method you can deserialize like this:
public PriceParser updatePrices()
{
var json = File.ReadAllText("test.json");
currentPrices = JsonConvert.DeserializeObject<Data>(json);
return this;
}
Here is how you would dump out the data:
PriceParser parser = new PriceParser();
parser.updatePrices();
foreach (var defindex in parser.currentPrices.response.prices)
{
Console.WriteLine("defindex: " + defindex.Key);
foreach (var quality in defindex.Value)
{
Console.WriteLine("\t quality: " + quality.Key);
foreach (var price in quality.Value)
{
Console.WriteLine("\t\t index: " + price.Key);
Console.WriteLine("\t\t\t current price:");
Console.WriteLine("\t\t\t\t currency: " + price.Value.current.currency);
Console.WriteLine("\t\t\t\t value: " + price.Value.current.value);
Console.WriteLine("\t\t\t\t value_high: " + price.Value.current.value_high);
if (price.Value.previous != null)
{
Console.WriteLine();
Console.WriteLine("\t\t\t previous price:");
Console.WriteLine("\t\t\t\t currency: " + price.Value.previous.currency);
Console.WriteLine("\t\t\t\t value: " + price.Value.previous.value);
Console.WriteLine("\t\t\t\t value_high: " + price.Value.previous.value_high);
}
}
}
}
And here is the output of the above:
defindex: 35
quality: 3
index: 0
current price:
currency: metal
value: 0.33
value_high: 0.66
quality: 11
index: 0
current price:
currency: keys
value: 39
value_high: 41
previous price:
currency: keys
value: 37
value_high: 39
defindex: 5002
quality: 6
index: 0
current price:
currency: usd
value: 0.39
value_high: 0.42
defindex: 5022
quality: 6
index: 1
current price:
currency: metal
value: 1.33
value_high: 1.55
Related
Coming here after learning about C# classes Constructors and ArrayLists so that not to put a completely dumb question here :)
I'm trying to Deserialize below Nested Lists of JSON returned from an API GET call as below:
I've been able to get the value from the empArra (Field: Emp), but subsequent lists like yearArray, prod and sale are not returning there values.
Can you please look into the below code that where is it doing wrong?
JSON
[
{
"employee":"156718100",
"availability":[
{
"year":2018,
"sales":{
"availability":"Maybe",
"reason":""
},
"prod":{
"availability":"Maybe",
"reason":""
}
},
{
"year":2019,
"sales":{
"availability":"Maybe",
"reason":""
},
"prod":{
"availability":"Maybe",
"reason":""
}
},
{
"year":2020,
"sales":{
"availability":"Maybe",
"reason":""
},
"top":{
"availability":"Maybe",
"reason":""
}
},
{
"year":2021,
"sales":{
"availability":"Maybe",
"reason":""
},
"prod":{
"availability":"Maybe",
"reason":""
}
}
]
}
]
Classes
public class sale
{
public string SaleAvailability { get; set; }
public string SaleReason { get; set; }
public sale(string pSaleAvailability, string pSaleReason)
{
this.SaleAvailability = pSaleAvailability;
this.SaleReason = pSaleReason;
}
public override string ToString()
{
return string.Format("{0} {1}", SaleAvailability, SaleReason);
}
}
public class prod
{
public string ProdAvailability { get; set; }
public string ProdReason { get; set; }
public prod(string pProdAvailability, string pProdReason)
{
this.ProdAvailability = pProdAvailability;
this.ProdReason = pProdReason;
}
public override string ToString()
{
return string.Format("{0} {1}", ProdAvailability, ProdReason);
}
}
public class yearArray
{
public int Year { get; set; }
public yearArray(int pYear)
{
this.Year = pYear;
}
List<sale> Sale { get; set; } = new List<sale>();
List<prod> Prod { get; set; } = new List<prod>();
}
public class rootAvailability
{
public List<yearArray> YearArray { get; set; } = new List<yearArray>();
}
public class empArray
{
public string Emp { get; set; }
public List<rootAvailability> RootAvailability { get; set; } = new List<rootAvailability>();
}
public class rootArray
{
public List<empArray> EmpArrays { get; set; } = new List<empArray>();
}
main() method
(After getting the response from API)
IRestResponse response = client.Execute<rootArray>(request);
//Console.WriteLine(response.Content);
List<rootArray> rootArrays = JsonConvert.DeserializeObject<List<rootArray>>(response.Content);
List<empArray> empArrays = JsonConvert.DeserializeObject<List<empArray>>(response.Content);
List<rootAvailability> rootAvailabilities = JsonConvert.DeserializeObject<List<rootAvailability>>(response.Content);
List<yearArray> yearArrays = JsonConvert.DeserializeObject<List<yearArray>>(response.Content);
List<sale> clsSale = JsonConvert.DeserializeObject<List<sale>>(response.Content);
List<prod> clsProd = JsonConvert.DeserializeObject<List<prod>>(response.Content);
foreach (var rootitem in rootArrays)
{
foreach (var emparrayitem in empArrays)
{
Console.WriteLine("NSN: " + emparrayitem.Emp);
foreach (var rootavailabbilitiesitem in rootAvailabilities)
{
foreach (var yearArrayItem in yearArrays)
{
Console.WriteLine("Year: " + yearArrayItem.Year);
foreach (var saleItem in clsSale)
{
Console.WriteLine("SaleAvailability: " + saleItem.SaleAvailability);
Console.WriteLine("SaleReason: " + saleItem.SaleReason);
}
foreach (var prodItem in clsProd)
{
Console.WriteLine("SaleAvailability: " + prodItem.ProdAvailability);
Console.WriteLine("SaleReason: " + prodItem.ProdReason);
}
}
}
}
}
Results
Emp: 159252663
Year: 0
SaleAvailability:
SaleReason:
SaleAvailability:
SaleReason:
You have two problems with your approach:
You want to deserialize the same source over and over again (response.Content) for different class instances. It can be deserialized into one object type: your top level entity.
Your data model does not reflect your data. For example YearArray should have a single Prod and Sale property not a list of them.
You have several options how to fix it. Let me share with you the two most common ones:
With proper naming
Your object model should look like this:
public class Sale
{
public string Availability { get; set; }
public string Reason { get; set; }
}
public class Prod
{
public string Availability { get; set; }
public string Reason { get; set; }
}
public class MidLevel
{
public int Year { get; set; }
public Sale Sales { get; set; }
public Prod Top { get; set; }
}
public class TopLevel
{
public string Employee { get; set; }
public List<MidLevel> Availability { get; set; } = new List<MidLevel>();
}
Then all you need to do is to call the following command:
var result = JsonConvert.DeserializeObject<TopLevel[]>(json);
Now, your result will be populated with all the data.
With JsonProperty
If you don't want to use the same names in your domain model which is used in the json then you can define the mapping between these two worlds via JsonProperty attributes.
Now your domain model can look like this:
public class SalesInformation
{
[JsonProperty(PropertyName = "availability")]
public string Avail { get; set; }
[JsonProperty(PropertyName = "reason")]
public string Reasoning { get; set; }
}
public class ProdInformation
{
[JsonProperty(PropertyName = "availability")]
public string Availability { get; set; }
[JsonProperty(PropertyName = "reason")]
public string Reasoning { get; set; }
}
public class MidLevel
{
[JsonProperty(PropertyName = "year")]
public int AvailYear { get; set; }
[JsonProperty(PropertyName = "sales")]
public SalesInformation SalesInfos { get; set; }
[JsonProperty(PropertyName = "top")]
public ProdInformation ProdInfos { get; set; }
}
public class TopLevel
{
[JsonProperty(PropertyName = "employee")]
public string Emp { get; set; }
[JsonProperty(PropertyName = "availability")]
public List<MidLevel> Availabilities { get; set; } = new List<MidLevel>();
}
The usage would be exactly the same:
var result = JsonConvert.DeserializeObject<TopLevel[]>(json);
UPDATE: How to display data
To represent hierarchy in a console application can be achieved in may ways. Here I will use indentation. I've introduced the following tiny helper method:
public static void WriteWithIndent(int level, string message) => Console.WriteLine("".PadLeft(level * 2) + message);
With this in hand the data visualization could be achieved in the following way:
var result = JsonConvert.DeserializeObject<TopLevel[]>(json);
foreach (var topLevel in result)
{
Console.WriteLine($"Employee: {topLevel.Emp}");
foreach (var midLevel in topLevel.Availabilities)
{
WriteWithIndent(1, $"Year: {midLevel.AvailYear}");
WriteWithIndent(1, "Sales:");
WriteWithIndent(2, $"Avail: {midLevel.SalesInfos.Avail}");
WriteWithIndent(2, $"Reason: {midLevel.SalesInfos.Reasoning}");
WriteWithIndent(1, "Top:");
WriteWithIndent(2, $"Avail: {midLevel.ProdInfos.Avail}");
WriteWithIndent(2, $"Reason: {midLevel.ProdInfos.Reasoning}");
}
}
The printed output will look like this:
Employee: 156718100
Year: 2018
Sales:
Avail: Maybe
Reason:
Top:
Avail: Maybe
Reason:
Year: 2019
Sales:
Avail: Maybe
Reason:
Top:
Avail: Maybe
Reason:
Year: 2020
Sales:
Avail: Maybe
Reason:
Top:
Avail: Maybe
Reason:
Year: 2021
Sales:
Avail: Maybe
Reason:
Top:
Avail: Maybe
Reason:
Here is my json data
{
"found": 501,
"posts": [
{
"ID": 2500,
"site_ID": 1,
"date": "2014-09-26T15:58:23-10:00",
"modified": "2014-09-26T15:58:23-10:00",
"title": "DOD HQ Visitors Parking",
"metadata": [
{
"id": "15064",
"key": "city",
"value": "Honolulu County"
},
{
"id": "15067",
"key": "country",
"value": "US"
},
{
"id": "15062",
"key": "floor_level",
"value": "Ground Floor"
}
]
}
],
"_headers": {
"Date": "Fri, 13 Feb 2015 09:21:55 GMT",
"Content-Type": "application/json"
}
}
I used model class generated from http://json2csharp.com/
public class Metadata
{
public string id { get; set; }
public string key { get; set; }
public string value { get; set; }
}
public class Post
{
public int ID { get; set; }
public int site_ID { get; set; }
public string date { get; set; }
public string modified { get; set; }
public string title { get; set; }
public List<Metadata> metadata { get; set; }
}
public class Headers
{
public string Date { get; set; }
public string __invalid_name__Content-Type { get; set; }
}
public class RootObject
{
public int found { get; set; }
public List<Post> posts { get; set; }
public Headers _headers { get; set; }
}
I don't know if this is the correct way of Parsing JSON array, I want to put the data collected into a ObservableCollection to be used in binding my Listview in XAML Page.
public class SpacesViewModel
{
public ObservableCollection<Space> Spaces { get; set; }
public SpacesViewModel()
{
Spaces = new ObservableCollection<Space>();
this.LoadSpaces();
}
async private void LoadSpaces()
{
var client = new HttpClient();
string json = await client.GetStringAsync("http://localhost/allspaces.json");
var resultObjects = AllChildren(JObject.Parse(json))
.First(c => c.Type == JTokenType.Array && c.Path.Contains("posts"))
.Children<JObject>();
foreach (JObject post in resultObjects)
{
Debug.WriteLine(post["title"]);
foreach (var metadata in post["metadata"])
{
var key = metadata["key"].ToString();
if (key == "city")
Debug.WriteLine(metadata["value"].ToString());
if (key == "street_address")
Debug.WriteLine(metadata["value"].ToString());
}
}
}
private static IEnumerable<JToken> AllChildren(JToken json)
{
foreach (var c in json.Children())
{
yield return c;
foreach (var cc in AllChildren(c))
{
yield return cc;
}
}
}
}
A possible approach is to use the Newtonsofts Json.NET libary. You can easily get it as nuget. After adding the nuget you can simply parse a collection (nested collections are working as well) with:
using Newtonsoft.Json;
...
string json = await client.GetStringAsync("http://localhost/allspaces.json");
List<Post> parsedPosts = JsonConvert.DeserializeObject<List<Post>>(json);
After getting the posts as List, you can add them to your ObservableCollection.
This class I created myself:
public class member
{
public string account_name { get; set; }
public long account_id { get; set; }
public Rootobject[] rootobject { get; set; }
}
This are the classes VS created for me autmatically using an example JSON answer:
public class Rootobject
{
public string status { get; set; }
public Meta meta { get; set; }
public Data data { get; set; }
}
public class Meta
{
public int count { get; set; }
}
public class Data
{
public _507888780[] _507888780 { get; set; }
}
public class _507888780
{
public All all { get; set; }
public int tank_id { get; set; }
}
public class All
{
public int spotted { get; set; }
public int hits_percents { get; set; }
public int wins { get; set; }
...
}
A small part of the JSON response from the API server I use looks like this:
{
"status": "ok",
"meta": {
"count": 1
},
"data": {
"507888780": [
{
"all": {
"spotted": 467,
"hits_percents": 83,
"wins": 281,
},
"tank_id": 2849
},
{
"all": {
"spotted": 224,
"hits_percents": 63,
"wins": 32,
},
"tank_id": 9473
},
}
}
This is the code I use to read out the tanks a member has (including all the stats) where Request(string) is just the http request.
private List<member> memberlist = new List<member>(100);
private void DoStuff()
{
memberlist = JsonConvert.DeserializeObject<List<member>>(result_member);
foreach (var member in memberlist)
{
string result_tank = Request("https://api.worldoftanks.eu/wot/tanks/stats/?application_id=" + application_id + "&account_id=" + member.account_id + "&tank_id=" + tanks + "&fields=all.battles%2C+all.wins%2C+all.damage_dealt%2C+all.frags%2C+all.hits_percents%2C+all.piercings%2C+all.shots%2C+all.spotted%2C+all.survived_battles%2C+all.tanking_factor");
var Rootobject = JsonConvert.DeserializeObject<Rootobject>(result_tank);
foreach (var tank in _507888780)
{
richTextBox1.Text += Rootobject.data._507888780[tank].tank_id + Rootobject.data._507888780[tank].all.spotted.ToString() + "...";
}
}
}
Now, I want to be able to search up all the different tanks including their stats for all members. Right now I'm getting the error in the line I want to print "Type Tank_Statistics._507888780 cannot be implicitly converted to int." Earlier on I alos got an error with a missing IEnumerable which I dont have right now though..
Anyways .. I can't make it work somehow.. it would be very kind if someone would be able to help me on this ;)
Seems that you should replace this
richTextBox1.Text += Rootobject.data._507888780[tank].tank_id + Rootobject.data._507888780[tank].all.spotted.ToString() + "...";
to this
richTextBox1.Text += tank.tank_id + tank.all.spotted.ToString() + "...";
How can I query (to see if a property exists) and enumerate (the array property) found within a complex JSON object using using JSON.NET in C# ?
I am receiving a complex JSON object from an API with a variable number/type of properties.
I keep reading the JSON.Net Documentation, reviewing samples, etc. but not gotten far and am lost in JObject, JArray, JToken, using dynamic, etc...
I want to find the pageResponses.scriptOutput property, verify it contains and .items[] array and then enumerate/iterate the array.
Edit
I made progress and found typo in JSON data example.
But how can I query/enumerate the child objects using key names, e.g.(item.location, item.timestamp) ?
string json = File.ReadAllText(#"Output.json");
JObject jObj = JObject.Parse(json);
IList<JToken> items = jObj["pageResponses"][0]["scriptOutput"]["items"].ToList();
foreach (JToken item in items){
Console.WriteLine(item["location"]);
}
/*** Console Output ***/
// Austin, TX
// Anaheim, CA
// Adams, MN
// Barstow, CA
var varItems = from o in jObj["pageResponses"][0]["scriptOutput"]["items"].ToList() select o;
foreach (var item in varItems){
Console.WriteLine(item["timestamp"]);
}
/*** Console Output ***/
// 2016 - 05 - 03 19:53
// 2016 - 05 - 04 04:10
// 2016 - 05 - 04 08:18
// 2016 - 05 - 01 12:26
(JSON sample below trimmed down for brevity)
{
"meta": {
"outputAsJson": true,
"backend": {
"os": "linux",
"id": "10.240.0.3_2",
"requestsProcessed": 8
}
},
"pageResponses": [
{
"pageRequest": {
"renderType": "script",
"outputAsJson": true
},
"frameData": {
"name": "",
"childCount": 1
},
"events": [
{
"key": "navigationRequested",
"time": "2016-05-06T13:43:30.344Z"
},
{
"key": "navigationRequested",
"time": "2016-05-06T13:43:31.131Z"
}
],
"scriptOutput": {
"items": [
{
"location": "Austin, TX",
"timestamp": "2016-05-03 19:53",
"title": "User Login"
},
{
"location": "Anaheim, CA",
"timestamp": "2016-05-04 04:10",
"title": "User Logout"
},
{
"location": "Adams, MN",
"timestamp": "2016-05-04 08:18",
"title": "User Login"
},
{
"location": "Barstow, CA",
"timestamp": "2016-05-01 12:26",
"title": "User Logout"
}
]
},
"statusCode": 200
}
],
"statusCode": 200,
"content": {
"name": "content.json",
"encoding": "utf8"
},
"originalRequest": {
"pages": [
{
"renderType": "script",
"outputAsJson": true
}
]
}
}
I suggest creating a proxy class (I used json2csharp):
public class Backend
{
public string os { get; set; }
public string id { get; set; }
public int requestsProcessed { get; set; }
}
public class Meta
{
public bool outputAsJson { get; set; }
public Backend backend { get; set; }
}
public class PageRequest
{
public string renderType { get; set; }
public bool outputAsJson { get; set; }
}
public class FrameData
{
public string name { get; set; }
public int childCount { get; set; }
}
public class Event
{
public string key { get; set; }
public string time { get; set; }
}
public class ScriptOutput
{
public List<object> items { get; set; }
}
public class PageRespons
{
public PageRequest pageRequest { get; set; }
public FrameData frameData { get; set; }
public List<Event> events { get; set; }
public ScriptOutput scriptOutput { get; set; }
public int statusCode { get; set; }
}
public class Content
{
public string name { get; set; }
public string encoding { get; set; }
}
public class Page
{
public string renderType { get; set; }
public bool outputAsJson { get; set; }
}
public class OriginalRequest
{
public List<Page> pages { get; set; }
}
public class RootObject
{
public Meta meta { get; set; }
public List<PageRespons> pageResponses { get; set; }
public int statusCode { get; set; }
public Content content { get; set; }
public OriginalRequest originalRequest { get; set; }
}
Then deserialize it:
var obj = JsonConvert.DeserializeObject<RootObject>(json);
if (obj != null && obj.pageResponses != null)
{
foreach (var pageResponse in obj.pageResponses)
{
if (pageResponse.scriptOutput == null)
continue;
foreach (var item in pageResponse.scriptOutput.items)
{
Console.WriteLine(item);
}
}
}
I do this with a couple of Extension Methods and I use JsonConvert.DeserializeObject.
Code snippets below.
Usage
ExpandoObject data = JsonConvert.DeserializeObject<ExpandoObject>(jsonString);
if(data.HasProperty("propertyToCheck"))
{
object[] objects = data.Get<object[]>("propertyToCheck");
}
In the snippet above I check a property exists, then I assign it to a .Net type, in this case an object array. Though it can be any type so long as it's sane.
Extension Methods
public static bool HasProperty(this ExpandoObject value, string property)
{
bool hasProp = false;
if (((IDictionary<String, object>)value).ContainsKey(property))
{
hasProp = true;
}
return hasProp;
}
public static T Get<T>(this ExpandoObject value, string property)
{
return (T)((IDictionary<String, dynamic>)value)[property];
}
Quick, easy and to the point!
I have this Json:
{
'TypeName': 'MyType',
'Values':
{
value1: 12,
value2: 'asdf',
value3: 32.2
}
}
The values can be either strings or numbers, and the amount of values is not set (so it could be 3, but also 5 or 12). I have a method in MVC:
class CoolObject{
public string TypeName { get; set; }
public ?????? Values { get; set; }
}
[HttpPost]
public ActionResult DoStuff(CoolObject values){
//Do cool stuff with cool object
}
What type of object should CoolObject include for this to work??
Your JSON is not a valid JSON, it should look more like this:
{
"TypeName": "MyType",
"Values": {
"value1": 12,
"value2": "asdf",
"value3": 32.2
}
}
Or you can have array there. But let's assume it is like this one. Then you can try this classes to work with that:
class CoolObjectValues
{
public object value1 { get; set; }
public object value2 { get; set; }
public object value3 { get; set; }
}
class CoolObject
{
public string TypeName { get; set; }
public CoolObjectValues Values { get; set; }
}
Note that when you want to work with values inside CoolObjectValues, you would want to determine what the values type is. For that you can do something like this:
int intValue;
if (int.TryParse(co.Values.value1.ToString(), out intValue))
{
// it was int and you now have its value in intValue
}
else
{
// it was string
}
Update:
For uncertain number of values:
{
"TypeName": "MyType",
"Values": {
"value1": 12,
"value2": "asdf",
"value3": 32.2,
...
}
}
Your classes will look like this:
class CoolObject
{
public string TypeName { get; set; }
public Dictionary<string, object> Values { get; set; }
}
and you can work with it like this:
CoolObject co = JsonConvert.DeserializeObject<CoolObject>(jsonData);
foreach (KeyValuePair<string, object> kvp in co.Values)
Console.WriteLine("{0} = {1}", kvp.Key, kvp.Value);
You could send the integers over as strings, then have logic on the server side to cast them to determine if they are numeric, and cast them to the correct type if they are.
// Server-side CLR object
class CoolObject
{
public string TypeName { get; set; }
public IEnumerable<string> Values { get; set; }
}
// Client-side JSON sent to the server
{
TypeName: "MyType",
Values: [
"12",
"asdf",
"32.2"
]
}