I am trying to Deserialize a JSON response from Google Maps Geocode.
Those are the classes I coded based on the response.
public class GoogleGeoCodeResponse
{
public string status { get; set; }
public results[] results { get; set; }
}
public class results
{
public address_component[] address_components { get; set; }
public String formatted_address { get; set; }
public Geometry geometry { get; set; }
public Boolean partial_match { get; set; }
public string[] types { get; set; }
}
public class address_component
{
public String long_name { get; set; }
public String short_name { get; set; }
public String[] types { get; set; }
}
public class Geometry
{
public bounds_viewport bounds { get; set; }
public LatLong location { get; set; }
public String location_type { get; set; }
}
public class bounds_viewport
{
public LatLong northeast { get; set; }
public LatLong southwest { get; set; }
}
public class LatLong
{
public double lon { get; set; }
public double lat { get; set; }
}
On my form button click I use this code.
var json = new WebClient().DownloadString("url");
GoogleGeoCodeResponse test = JsonConvert.DeserializeObject<GoogleGeoCodeResponse>(json);
MessageBox.Show(test.results[0].geometry.location.lat + " / " + test.results[0].geometry.location.lon);
Then I wanted to see if I get the correct information but I'm getting:
"Error 1 Inconsistent accessibility: property type 'WindowsFormsApplication1.results[]' is less accessible than property 'WindowsFormsApplication1.GoogleGeoCodeResponse.results'"
I've looked a bunch of post with the same problem but they always had something private or internal somewhere that would lead to this error. As you can see everything I declared is public.
I don't understand why something public inside of a class would be more accessible than the class itself.
I am fairly new to C# and JSON so this might be something really simple.
Ok so I took all of my class and put the code out of my form's class and it worked. I put it back in the class and it still worked I don't know what I have changed, maybe it was just bugged or something anyway thanks for the help!
Related
I've read and reread articles online on how to do this, but it's probably something simple. I'm trying to learn how to process a json response from an API call. I have a simple method I call from Main()
public async void apiTestCall()
{
var httpCall = new HttpClient();
var uri = new Uri("https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key=DEMO_KEY");
var result = new DataModel();
var response = await httpCall.GetStringAsync(uri);
result = JsonSerializer.Deserialize<DataModel>(response);
I am expecting "result" to be a DataModel object with the data populated. Right now, there is nothing.
Here is the DataModel class
using System.Collections.Generic;
namespace APITestProject
{
class DataModel
{
public class Camera
{
public int id { get; set; }
public string name { get; set; }
public int rover_id { get; set; }
public string full_name { get; set; }
}
public class Rover
{
public int id { get; set; }
public string name { get; set; }
public string landing_date { get; set; }
public string launch_date { get; set; }
public string status { get; set; }
}
public class Photo
{
public int id { get; set; }
public int sol { get; set; }
public Camera camera { get; set; }
public string img_src { get; set; }
public string earth_date { get; set; }
public Rover rover { get; set; }
}
public class Example
{
public IList<Photo> photos { get; set; }
}
}
}
Any suggestions would be greatly appreciated.
Edit #1: Here is the first 3 entries in the json. I didn't want to post the whole thing but the URL is valid for anyone to run and see the response.
{"photos":[{"id":424926,"sol":1000,"camera":{"id":22,"name":"MAST","rover_id":5,"full_name":"Mast Camera"},"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631200305217E01_DXXX.jpg","earth_date":"2015-05-30","rover":{"id":5,"name":"Curiosity","landing_date":"2012-08-06","launch_date":"2011-11-26","status":"active"}},{"id":424927,"sol":1000,"camera":{"id":22,"name":"MAST","rover_id":5,"full_name":"Mast Camera"},"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631190503679E04_DXXX.jpg","earth_date":"2015-05-30","rover":
Edit #2: I made some changes based on the comments so far and I used the Paste > Special > JSON as Classes and removed the "wrapper" class. Now I get the populated object. FYI, here is the new class VS generated:
namespace APITestProject
{
public class DataModel
{
public Photo[] photos { get; set; }
}
public class Photo
{
public int id { get; set; }
public int sol { get; set; }
public Camera camera { get; set; }
public string img_src { get; set; }
public string earth_date { get; set; }
public Rover rover { get; set; }
}
public class Camera
{
public int id { get; set; }
public string name { get; set; }
public int rover_id { get; set; }
public string full_name { get; set; }
}
public class Rover
{
public int id { get; set; }
public string name { get; set; }
public string landing_date { get; set; }
public string launch_date { get; set; }
public string status { get; set; }
}
}
Can you try the following amendment
var result = JsonSerializer.Deserialize<DataModel.Example>(response);
as it looks like DataModel.Example is actually the class that you are trying to deserialize to based on the response that comes from the following call -
https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key=DEMO_KEY
i.e. It returns an object containing an array of photo objects as you have defined them. As someone else mentioned, no need to encapsulate all of these classes within another class.
You class DataModel just defines other classes, but do not instance them.
According to the data inside the response, you DataModel class should have at least Photos member. It could be type of List<Photo>
class DataModel {
...
public List<Photo> Photos { get; set; }
}
The Photos definition says, that there is some list of photos of Photo type to be expected.
I've recently completed the Treehouse online course "C# Streams and Data Processing" and one of the lessons in the course showed how to use Webclient to query an API and parse the data returned. The example shown used a Microsoft Bing API to retrieve data about soccer players, but I'm trying to create my own project using a different API, which is the USGS Earthquake Catalog. It shows data about recent earthquakes:
USGS Earthquake Catalog
I've got the Webclient working and am getting the return data. I've created a new class for the data and have pasted it as JSON, but I'm not sure why I can't access the properties in my code?
what I'd like to be able to do is run a loop to iterate through the data and show the magnitude, location and time.
namespace Earthquakes
{
class Program
{
static void Main(string[] args)
{
GetEarthquakes();
}
public static void GetEarthquakes()
{
var webClient = new WebClient();
byte[] earthquakeBytes = webClient.DownloadData("https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2020-08-01&endtime=2020-08-02");
var serializer = new JsonSerializer();
using (var stream = new MemoryStream(earthquakeBytes))
using (var reader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(reader))
{
string jsonString = "";
var earthquake = JsonConvert.DeserializeObject<Earthquakes>(jsonString);
Console.WriteLine(earthquake.Metadata.title);
}
}
}
}
And the class:
namespace Earthquakes
{
public class Metadata
{
public long Generated { get; set; }
public string url { get; set; }
public string title { get; set; }
public int status { get; set; }
public string api { get; set; }
public int count { get; set; }
}
public class Properties
{
[JsonProperty("mag")]
public double Magnitude { get; set; }
[JsonProperty("place")]
public string Place { get; set; }
public object Time { get; set; }
public object Updated { get; set; }
public object Tz { get; set; }
public string Url { get; set; }
public string Detail { get; set; }
public int? Felt { get; set; }
public double? Cdi { get; set; }
public double? Mmi { get; set; }
public string Alert { get; set; }
public string Status { get; set; }
public int Tsunami { get; set; }
public int Sig { get; set; }
public string Net { get; set; }
public string Code { get; set; }
public string Ids { get; set; }
public string Sources { get; set; }
public string Types { get; set; }
public int? Nst { get; set; }
public double? Dmin { get; set; }
public double Rms { get; set; }
public double? Gap { get; set; }
public string MagType { get; set; }
public string Type { get; set; }
public string Title { get; set; }
}
public class Geometry
{
public string type { get; set; }
public List<double> coordinates { get; set; }
}
public class Feature
{
public string type { get; set; }
public Properties properties { get; set; }
public Geometry geometry { get; set; }
public string id { get; set; }
}
public class Earthquakes
{
public string Type { get; set; }
public Metadata Metadata { get; set; }
public List<Feature> Features { get; set; }
public List<double> Bbox { get; set; }
}
}
A few points:
It seems the main problem in your code is that you are completely ignoring the data you are downloading and instead you are deserializing an empty string. You can actually simplify your code quite a bit by using the DownloadString method instead of DownloadData. Then you can fetch the JSON and deserialize it in just a couple lines of code:
webClient.Encoding = Encoding.UTF8;
var json = webClient.DownloadString("https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2020-08-01&endtime=2020-08-02");
var quakes = JsonConvert.DeserializeObject<Earthquakes>(json);
You don't actually need to include properties for everything in the JSON if you are only interested in a few things. If you only want magnitude, place and time, then your model classes could be as simple as this:
class Earthquakes
{
public List<Feature> Features { get; set; }
}
class Feature
{
public Properties Properties { get; set; }
}
class Properties
{
[JsonProperty("mag")]
public decimal Magnitude { get; set; }
public string Place { get; set; }
[JsonProperty("time")]
public long TimeMs { get; set; }
public DateTime Time => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(TimeMs);
}
Also notice that I have added a convenience method to convert the time value from the JSON into a readable date.
You have a namespace called Earthquakes and a class Earthquakes within it. While this is legal in C#, it makes the code harder to understand. I would recommend renaming one or the other so they are not the same. For example, you could rename the namespace EarthquakeDemo or rename the class EarthquakeInfo. Up to you of course. Also, I would recommend making the GetEarthquakes() method return the earthquakes it fetches instead of being a void method. Then you can separate the fetch logic from the display logic.
Here is a working demo with the above changes applied and display logic added: https://dotnetfiddle.net/uRg48y
I'm struggling to pick up the data returned from the Geocoding REST call below (I've removed my app_id and app_code so you cannot run the url as it is below):
https://geocoder.api.here.com/6.2/geocode.json?app_id=[MY APP ID]&app_code=[MY APP CODE]&searchtext=chester
An example reponse can be found here:
https://developer.here.com/documentation/geocoder/topics/quick-start-geocode.html
I know it returns results but I cannot seem to get the results through to my C# application, I don't know if I'm building the class system incorrectly or whether there's something else I'm doing wrong. I've built an application reading the places api with no problem before but this one seems different somehow.
The call returns with a status of OK:
public void GeoCode()
{
// BUILD INITIAL STRING BASED ON PARAMETERS
string url = "https://geocoder.api.here.com/6.2/geocode.json?app_id=gpZ1Ym7rwuXs6Xj1TsD8&app_code=4UXmaGFTe30LttFgOY7iqQ&searchtext=" + tbLoc.text;
// RUN THE ASYNCRONOUS COMMAND (I THINK)
StartCoroutine(GetText(url));
}
IEnumerator GetText(string url)
{
// CREATE WEB REQUEST WITH SPECIFIED URL
UnityWebRequest www = UnityWebRequest.Get(url);
// RETURN RESULTS FROM ASYNC COMMAND
yield return www.SendWebRequest();
// PARSE JSON RESPONSE
RootObject RootObject = JsonUtility.FromJson<RootObject>(www.downloadHandler.text);
// IF 200 (STATUS OKAY) GATHER RESULTS, ELSE RETURN ERROR
if (www.responseCode == 200)
{
BuildArray(RootObject);
}
else
{
Debug.Log("Call Failed With Error: " + www.responseCode.ToString());
}
}
private void BuildArray(RootObject RootObject)
{
print(RootObject.Response.View[0].Result[0].Location.Address.Label);
}
It attempts to run the BuildArray function but this returns a null reference. (I'm just using the NextPageInformation as the most basic option to test)
Below is the class structure I have built to deal with the JSON:
public class Address
{
public string Label { get; set; }
}
public class Location
{
public Address Address { get; set; }
}
public class Result
{
public Location Location { get; set; }
}
public class View
{
public List<Result> Result { get; set; }
}
public class Response
{
public List<View> View { get; set; }
}
public class RootObject
{
public Response Response { get; set; }
}
Can someone please help?
Thank you
If you want to parse only one key value pair from json then creating class structure will tedious job for you.
Instead of creating class structure you can Querying a json with Newtonsoft.
Parse your json.
Querying your parsed json to retrieve Label
value.
//1
JToken jToken = JToken.Parse(jsonString);
//2
string label = jToken["Response"]["View"][0]["Result"][0]["Location"]["Address"]["Label"].ToObject<string>();
Console.WriteLine(label);
Console.ReadLine();
Output: (from your sample json provided in link)
Note: You need to install newtonsoft.json package from nuget and add below namespace to your program.
using Newtonsoft.Json.Linq;
Edit:
If you want parsed your full json then first create a class structure like below
public class MetaInfo
{
public DateTime Timestamp { get; set; }
}
public class MatchQuality
{
public int City { get; set; }
public List<double> Street { get; set; }
public int HouseNumber { get; set; }
}
public class DisplayPosition
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class NavigationPosition
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class TopLeft
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class BottomRight
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class MapView
{
public TopLeft TopLeft { get; set; }
public BottomRight BottomRight { get; set; }
}
public class AdditionalData
{
public string value { get; set; }
public string key { get; set; }
}
public class Address
{
public string Label { get; set; }
public string Country { get; set; }
public string State { get; set; }
public string County { get; set; }
public string City { get; set; }
public string District { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
public string PostalCode { get; set; }
public List<AdditionalData> AdditionalData { get; set; }
}
public class Location
{
public string LocationId { get; set; }
public string LocationType { get; set; }
public DisplayPosition DisplayPosition { get; set; }
public List<NavigationPosition> NavigationPosition { get; set; }
public MapView MapView { get; set; }
public Address Address { get; set; }
}
public class Result
{
public int Relevance { get; set; }
public string MatchLevel { get; set; }
public MatchQuality MatchQuality { get; set; }
public string MatchType { get; set; }
public Location Location { get; set; }
}
public class View
{
public string _type { get; set; }
public int ViewId { get; set; }
public List<Result> Result { get; set; }
}
public class Response
{
public MetaInfo MetaInfo { get; set; }
public List<View> View { get; set; }
}
public class RootObject
{
public Response Response { get; set; }
}
And then you can deserialized your json like.
RootObject rootObject = JsonConvert.DeserializeObject<RootObject>(jsonString);
Note: You need to add below namespace to your program.
using Newtonsoft.Json;
I personally had problems parsing nested json objects with JsonUtility. So I switched to Newtonsoft Json and it works really well.
In your case you need to mark the classes as serialized using [Serializable] see here
[Serializable]
public class Address
{
public string Label;
}
I've been reading threads about deserializing json and tried a number of ways to achieve it but can't seem to make this work.
Here's what I've done so far:
Class
public class oVesselMovement : oVMLocation, IWPConditions, IVessel, IStatus
{
public int wpID { get; set; }
public string charts { get; set; }
public string latNS { get; set; }
public string longEW { get; set; }
public string course { get; set; }
public double toNextWPNM { get; set; }
public double toGoNM { get; set; }
public string eda { get; set; }
public string eta { get; set; }
}
Root Class
public class RootVM
{
public List<oVesselMovement> jsnObj { get; set; }
}
JSON
{"jsnObj":[{"vmID":"1","charts":"2111","latNS":"10°29.10 N","longEW":"123°25.83 E","course":"420°","toNextWPNM":0,"toGoNM":"27","eda":"12-15-2016","eta":"11:01"},{"vmID":"2","charts":"2211","latNS":"11°29.10 N","longEW":"124°25.83 E","course":"420°","toNextWPNM":0,"toGoNM":"27","eda":"12-15-2016","eta":"11:01"}]}
Deserialization
[WebMethod]
public void saveVMDT(string jsnObj)
{
RootVM rootObj = new JavaScriptSerializer().Deserialize<RootVM>(jsnObj);
foreach (var obj in rootObj.jsnObj)
{
try
{
...
}
I've also tried converting the List<oVesselMovement> jsnObj { get; set; } to
oVesselMovement[] jsnObj but to no avail.
I keep getting this error:
"{"Message":"Type \u0027System.String\u0027 is not supported for deserialization of an array."....
Any help would be appreciated.
toGoNM is a string, not a number. You need to use the proper types, fix it either on the C# side, or the JSON side. You have wpID in the C# class, but vmID in the JSON; and again, with the wrong type.
I'm not sure what JavaScriptSerializer does internally, but newtonsoft's JToken handles this fine. The following code works:
public class oVesselMovement //notice no ancestor class or interfaces
{
public int wpID { get; set; }
public string charts { get; set; }
public string latNS { get; set; }
public string longEW { get; set; }
public string course { get; set; }
public double toNextWPNM { get; set; }
public double toGoNM { get; set; }
public string eda { get; set; }
public string eta { get; set; }
}
public class RootVM
{
public List<oVesselMovement> jsnObj { get; set; }
}
public class MakeItSo
{
public const string json = "{\"jsnObj\":[{\"vmID\":\"1\",\"charts\":\"2111\",\"latNS\":\"10°29.10 N\",\"longEW\":\"123°25.83 E\",\"course\":\"420°\",\"toNextWPNM\":0,\"toGoNM\":\"27\",\"eda\":\"12-15-2016\",\"eta\":\"11:01\"},{\"vmID\":\"2\",\"charts\":\"2211\",\"latNS\":\"11°29.10 N\",\"longEW\":\"124°25.83 E\",\"course\":\"420°\",\"toNextWPNM\":0,\"toGoNM\":\"27\",\"eda\":\"12-15-2016\",\"eta\":\"11:01\"}]}";
public void SaveVmd()
{
RootVM rootObj = JToken.Parse(json).ToObject<RootVM>();
Console.WriteLine($"Parsed {rootObj.jsnObj.Count} vessel movement objects");
}
}
class Program
{
static void Main(string[] args)
{
var makeitso = new MakeItSo();
makeitso.SaveVmd();
}
}
Note however, that I don't have oVesselMovement as an ancestor of oVMLocation or an implementor of any interfaces. I believe the problem may lie in fields or properties on oVMLocation that are not in the json.
I'm new to JSON.NET, and I've been playing with the new Marvel API that was recently released.
When I call this API it will return the following JSON Data Structure:-
{
"code": 200,
"status": "Ok",
"etag": "f0fbae65eb2f8f28bdeea0a29be8749a4e67acb3",
"data":
{
"offset": 0,
"limit": 20,
"total": 30920,
"count": 20,
"results": [{array of objects}}]
}
}
I can create Classes for this Data like this :
public class Rootobject
{
public int code { get; set; }
public string status { get; set; }
public string etag { get; set; }
public Data data { get; set; }
}
public class Data
{
public int offset { get; set; }
public int limit { get; set; }
public int total { get; set; }
public int count { get; set; }
public Result[] results { get; set; }
}
public class Result
{
}
Now, my issue. The Results that come back from the API can relate to different Objects, it could be results relating to Characters, Comics, Series etc. The objects all hold different properties.
I need to be able to swap out the Result Class properties based on the Entity Type that the results relate too?
Can this actually be done?
You can use var jObj = JObject.Parse(jsonString) then discover what object type it is by which properties are available on the object.
jObj["someComicSpecificProperty"] != null
However this is not full proof and will need to be done on a per object basis for the results array.
An alternate approach I have seen people use is to have a property on the object that is "typeName".
However the root cause of this problem is that you are trying to strongly type a property that is not strongly typed. I would really recommend splitting these different types of results out into different properties so that you don't have this problem.
As promised, I've posted the anser to this problem. It turns out that the JSON response has nested data covering all related data-types, very much like a relational database.
I found something really cool, I basically made a request to the API and converted its response to a string. I then used the debugger to take a copy of the contents to the clipboard.
I created a new Class and Called it MarvelResponse.
I added the NewtonSoft.Json directive to the file, and used the Paste Special option from Edit Menu in VS2012. Here you can paste the option "Paste as JSON CLasses".
After some minor tweaking here is what it provided :-
namespace Kaiser.Training.Data.JSONClasses
{
public class MarvelResponse
{
public int code { get; set; }
public string status { get; set; }
public string etag { get; set; }
public Data data { get; set; }
}
public class Data
{
public int offset { get; set; }
public int limit { get; set; }
public int total { get; set; }
public int count { get; set; }
public Result[] results { get; set; }
}
public class Result
{
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public DateTime modified { get; set; }
public Thumbnail thumbnail { get; set; }
public string resourceURI { get; set; }
public Comics comics { get; set; }
public Series series { get; set; }
public Stories stories { get; set; }
public Events events { get; set; }
public Url[] urls { get; set; }
}
public class Thumbnail
{
public string path { get; set; }
public string extension { get; set; }
}
public class Comics
{
public int available { get; set; }
public string collectionURI { get; set; }
public ComicResourceUriItem[] items { get; set; }
public int returned { get; set; }
}
public class ComicResourceUriItem
{
public string resourceURI { get; set; }
public string name { get; set; }
}
public class Series
{
public int available { get; set; }
public string collectionURI { get; set; }
public SeriesResourceItem[] items { get; set; }
public int returned { get; set; }
}
public class SeriesResourceItem
{
public string resourceURI { get; set; }
public string name { get; set; }
}
public class Stories
{
public int available { get; set; }
public string collectionURI { get; set; }
public StoriesResourceItem[] items { get; set; }
public int returned { get; set; }
}
public class StoriesResourceItem
{
public string resourceURI { get; set; }
public string name { get; set; }
public string type { get; set; }
}
public class Events
{
public int available { get; set; }
public string collectionURI { get; set; }
public EventsResourceUriItem[] items { get; set; }
public int returned { get; set; }
}
public class EventsResourceUriItem
{
public string resourceURI { get; set; }
public string name { get; set; }
}
public class Url
{
public string type { get; set; }
public string url { get; set; }
}
}
This was a huge help! Hope someone else finds it useful.