Reputation: 511
OK, I'll try to be short and specific. I am getting a JSON string from an public API that looks like this: (short version of it, took only 2 samples)
[{
"$type": "Tfl.Api.Presentation.Entities.Line, Tfl.Api.Presentation.Entities",
"id": "bakerloo",
"name": "Bakerloo",
"modeName": "tube",
"disruptions": [],
"created": "2017-03-16T15:56:01.01Z",
"modified": "2017-03-16T15:56:01.01Z",
"lineStatuses": [
{
"$type": "Tfl.Api.Presentation.Entities.LineStatus, Tfl.Api.Presentation.Entities",
"id": 0,
"statusSeverity": 10,
"statusSeverityDescription": "Good Service",
"created": "0001-01-01T00:00:00",
"validityPeriods": []
}
],
"routeSections": [],
"serviceTypes": [
{
"$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
"name": "Regular",
"uri": "/Line/Route?ids=Bakerloo&serviceTypes=Regular"
}
],
"crowding": {
"$type": "Tfl.Api.Presentation.Entities.Crowding, Tfl.Api.Presentation.Entities"
}
},
{
"$type": "Tfl.Api.Presentation.Entities.Line, Tfl.Api.Presentation.Entities",
"id": "central",
"name": "Central",
"modeName": "tube",
"disruptions": [],
"created": "2017-03-16T15:56:01.01Z",
"modified": "2017-03-16T15:56:01.01Z",
"lineStatuses": [
{
"$type": "Tfl.Api.Presentation.Entities.LineStatus, Tfl.Api.Presentation.Entities",
"id": 0,
"statusSeverity": 10,
"statusSeverityDescription": "Good Service",
"created": "0001-01-01T00:00:00",
"validityPeriods": []
}
],
"routeSections": [],
"serviceTypes": [
{
"$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
"name": "Regular",
"uri": "/Line/Route?ids=Central&serviceTypes=Regular"
},
{
"$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
"name": "Night",
"uri": "/Line/Route?ids=Central&serviceTypes=Night"
}
],
"crowding": {
"$type": "Tfl.Api.Presentation.Entities.Crowding, Tfl.Api.Presentation.Entities"
}
}]
so far so good, below I'll paste the relevant code that I'm using and trying to deserialize this JSON string into C# classes that I got from json2csharp free online service. The relevant code where I'm trying to achieve this is:
public async static Task<tubeStatusRootObject> GetTubeStatus(string url)
{
var http = new HttpClient();
var response = await http.GetAsync(url);
var result = await response.Content.ReadAsStringAsync(); //This is working
var deserializer = new DataContractJsonSerializer(typeof(tubeStatusRootObject));
var ms = new MemoryStream(Encoding.UTF8.GetBytes(result));
var data = (tubeStatusRootObject)deserializer.ReadObject(ms);
return data; //all "data" properties are null
}
as you can read the comments above, all I'm getting inside data before return is null.
The classes that were generated by json2csharp looks like this:
[DataContract]
public class Disruption
{
[DataMember]
public string type { get; set; }
[DataMember]
public string category { get; set; }
[DataMember]
public string categoryDescription { get; set; }
[DataMember]
public string description { get; set; }
[DataMember]
public string additionalInfo { get; set; }
[DataMember]
public string created { get; set; }
[DataMember]
public List<object> affectedRoutes { get; set; }
[DataMember]
public List<object> affectedStops { get; set; }
[DataMember]
public string closureText { get; set; }
[DataMember]
public bool? isWholeLine { get; set; }
}
[DataContract]
public class LineStatus
{
[DataMember]
public string type { get; set; }
[DataMember]
public int id { get; set; }
[DataMember]
public int statusSeverity { get; set; }
[DataMember]
public string statusSeverityDescription { get; set; }
[DataMember]
public string created { get; set; }
[DataMember]
public List<object> validityPeriods { get; set; }
[DataMember]
public string lineId { get; set; }
[DataMember]
public string reason { get; set; }
[DataMember]
public Disruption disruption { get; set; }
}
[DataContract]
public class ServiceType
{
[DataMember]
public string type { get; set; }
[DataMember]
public string name { get; set; }
[DataMember]
public string uri { get; set; }
}
[DataContract]
public class Crowding
{
[DataMember]
public string type { get; set; }
}
[DataContract]
public class tubeStatusRootObject
{
[DataMember]
public string type { get; set; }
[DataMember]
public string id { get; set; }
[DataMember]
public string name { get; set; }
[DataMember]
public string modeName { get; set; }
[DataMember]
public List<object> disruptions { get; set; }
[DataMember]
public string created { get; set; }
[DataMember]
public string modified { get; set; }
[DataMember]
public List<LineStatus> lineStatuses { get; set; }
[DataMember]
public List<object> routeSections { get; set; }
[DataMember]
public List<ServiceType> serviceTypes { get; set; }
[DataMember]
public Crowding crowding { get; set; }
}
obviously I just added the [DataContract]
's and [DataMember]
's there. Anyone with idea of what I am doing wrong and could help me
I've followed the example from Channel9
Please don't mark duplicate, as I've found a lot similar questions, some using newtonsoft json but I could not implement the solutions from there into my example
Upvotes: 2
Views: 3242
Reputation: 65586
As others have said, you need to deserialize into an array, not just a single instance of the type you've defined as the response is an array.
If you read the response into a string then Json.Net means you only need a single line response
var data= Newtonsoft.Json.JsonConvert.DeserializeObject<tubeStatusRootObject[]>(result);
this is compared with 3+lines when using DataContractJsonSerializer
var deserializer = new DataContractJsonSerializer(typeof(tubeStatusRootObject[]));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(result)))
{
var data = (tubeStatusRootObject[])deserializer.ReadObject(ms);
}
(Note the use of the using
wrapper to ensure the MemoryStream is disposed.)
If you read the HTTP response stream directly into the deserializer you don't get the LoC saving though.
using (var s = http.GetStreamAsync(url).Result)
using (var sr = new StreamReader(s))
using (var reader = new Newtonsoft.Json.JsonTextReader(sr))
{
var serializer = new Newtonsoft.Json.JsonSerializer();
var data = serializer.Deserialize<tubeStatusRootObject[]>(reader);
}
Versus
using (var stream = await response.Content.ReadAsStreamAsync())
{
var dcjs = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(tubeStatusRootObject[]));
var data= (tubeStatusRootObject[])dcjs.ReadObject(stream);
}
The other thing you may want to consider is performance. Json.Net claim the following
Upvotes: 1
Reputation: 247561
The sample data provided describes an array (tubeStatusRootObject[])
but when you try to deserialize you cast it to a single instance which is an invalid cast. that is why data
is null
.
Also no need to re-invent the wheel if there are tools available to simply the problem.
static http = new HttpClient(); //reuse httpclient instead of creating a new one each time.
public async static Task<tubeStatusRootObject[]> GetTubeStatus(string url) {
var response = await http.GetAsync(url);
var json = await response.Content.ReadAsStringAsync(); //This is working
var data = Newtonsoft.Json.JsonConvert.DeserializeObject<tubeStatusRootObject[]>(json);
return data;
}
Upvotes: 2
Reputation: 1609
It's a bit complicated object that you are getting.
When It's deserialized(the way you're doing now), it looks out for matching names of objects with same datatype as expected. If it does not find, deserialization fails and returns null. And it's pretty sure.
If not newtonsoft, you can either match the datatype of each nested object to some generic. Or need to perform some string operations to deserialize it yourself(Pretty complicated).
I would prefer using Newtonsoft.json for such objects
Upvotes: 1