Newtonsoft JSON Deserialize

Newtonsoft JSON Deserialize

You can implement a class that holds the fields you have in your JSON

class MyData
{
public string t;
public bool a;
public object[] data;
public string[][] type;
}

and then use the generic version of DeserializeObject:

MyData tmp = JsonConvert.DeserializeObject<MyData>(json);
foreach (string typeStr in tmp.type[0])
{
// Do something with typeStr
}

Documentation: Serializing and Deserializing JSON

Deserializing JSON to .NET object using Newtonsoft (or LINQ to JSON maybe?)

If you just need to get a few items from the JSON object, I would use Json.NET's LINQ to JSON JObject class. For example:

JToken token = JObject.Parse(stringFullOfJson);

int page = (int)token.SelectToken("page");
int totalPages = (int)token.SelectToken("total_pages");

I like this approach because you don't need to fully deserialize the JSON object. This comes in handy with APIs that can sometimes surprise you with missing object properties, like Twitter.

Documentation: Serializing and Deserializing JSON with Json.NET and LINQ to JSON with Json.NET

How to deserialize complex JSON with Newtonsoft?

This is indeed a complex schema so it's not easy to generate DTOs automatically. For that reason, you shouldn't use custom names for the properties. It makes it even harder to find problems.

  1. The near_earth_objects section is actually a dictionary of daily observations. Instead of creating a near_earth_objects class it's better to use a Dictionary<string,Observation[]>.
  2. links contains links to the current, next and previous pages in the feed. This means you can actually create one class and reuse it both at the root level and in daily observations

You can use a DTO generator tool to get started but the result will need modifications. Tools won't be able to recognize that near_earth_objects is a dictionary and can easily end up creating new types for every entry.

The DTOs

Using your JSON sample I created initial classes using Visual Studio's Paste as Json and then modified them to work properly.

public class Rootobject
{
public PageLinks links { get; set; }
public int element_count { get; set; }
public Dictionary<string,Observation[]> near_earth_objects { get; set; }
}

public class PageLinks
{
public string? next { get; set; }
public string? prev { get; set; }
public string self { get; set; }
}

The Observation class uses the same PageLinks class for the links property:

public class Observation
{
public PageLinks links { get; set; }
public string id { get; set; }
public string neo_reference_id { get; set; }
public string name { get; set; }
public string nasa_jpl_url { get; set; }
public float absolute_magnitude_h { get; set; }
public Estimated_Diameter estimated_diameter { get; set; }
public bool is_potentially_hazardous_asteroid { get; set; }
public Close_Approach_Data[] close_approach_data { get; set; }
public bool is_sentry_object { get; set; }
}

The rest of the classes require no modification:

public class Estimated_Diameter
{
public Kilometers kilometers { get; set; }
public Meters meters { get; set; }
public Miles miles { get; set; }
public Feet feet { get; set; }
}

public class Kilometers
{
public float estimated_diameter_min { get; set; }
public float estimated_diameter_max { get; set; }
}

public class Meters
{
public float estimated_diameter_min { get; set; }
public float estimated_diameter_max { get; set; }
}

public class Miles
{
public float estimated_diameter_min { get; set; }
public float estimated_diameter_max { get; set; }
}

public class Feet
{
public float estimated_diameter_min { get; set; }
public float estimated_diameter_max { get; set; }
}

public class Close_Approach_Data
{
public string close_approach_date { get; set; }
public string close_approach_date_full { get; set; }
public long epoch_date_close_approach { get; set; }
public Relative_Velocity relative_velocity { get; set; }
public Miss_Distance miss_distance { get; set; }
public string orbiting_body { get; set; }
}

public class Relative_Velocity
{
public string kilometers_per_second { get; set; }
public string kilometers_per_hour { get; set; }
public string miles_per_hour { get; set; }
}

public class Miss_Distance
{
public string astronomical { get; set; }
public string lunar { get; set; }
public string kilometers { get; set; }
public string miles { get; set; }
}

Testing the model

With this model, the following test passes:

[Fact]
public async Task GetFeed()
{
var client = new HttpClient();
var url = "http://www.neowsapp.com/rest/v1/feed?start_date=2021-12-09&end_date=2021-12-12&detailed=false&api_key=Na1sKwJGK1HVeOF4Yx8aLNp4u8ygT5GSSMF26HQ2";
var feed = await client.GetFromJsonAsync<Rootobject>(url);

Assert.Equal(76,feed.element_count);
var allObservations = feed.near_earth_objects
.SelectMany(p => p.Value)
.ToList();
Assert.Equal(76,allObservations.Count);
}

Deserializing JSON with Newtonsoft, using a specific class

Copy your JSON. Open Visual studio. Create new C# class file. Now Select below menu option:

Edit > Paste Special > Paste JSON as classes

This will create a class as below

public class Rootobject
{
public Datum[] data { get; set; }
}

public class Datum
{
public string type { get; set; }
public string id { get; set; }
}

Now change RootObject to jsonTask and deserialise as below

jsonTask test = JsonConvert.DeserializeObject<jsonTask>(strJSON);

Why does Newtonsoft JsonConvert not deserialize Json array fields?

The reason why you could not deserialize it directly is because your json and your data model has a mismatch.

"details":"[{\"field_id\":\"1142488407\",\"response\":\"256\",\"field_type\":\"text\"},..]"
  • In this case your details data type is string
  • So, you can NOT parse it directly as a collection of objects

if your json would look like this:

"details":[{\"field_id\":\"1142488407\",\"response\":\"256\",\"field_type\":\"text\"},..]
  • the the data type of the details field would be a collection

So, the very reason why did your second approach worked is because you have first deserialized the details then you deserialized the json array.

Deserialize JSON to different Object Types based on JSON content in C#

string JsonMessageFromDevice = "{"device_id":"DeviceTest","message_id":0,"port":3,"portValue":false,"time":"2021-08-25 10:18:51","response_status":"Send"}";

var oMycustomclassname = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(JsonMessageFromDevice);

or

var jobject = JObject.Parse(JsonMessageFromDevice);
var result = jobject["port"];
if (result != null)
{
ReadResponseMessage readResponseMessage = jobject.ToObject<ReadResponseMessage>();
}
else
{
EventMessage eventMessage = jobject.ToObject<EventMessage>();
}

OR Change model

class ReadResponseMessage  {

[JsonProperty("device_id")]
public string DeviceID {get; set;}

[JsonProperty("message_id")]
public int MessageID {get; set;}

[JsonProperty("port")]
public Dictionary<String,bool> Port{get; set;} // or List<string> port

[JsonProperty("time")]
public DateTime Time {get; set;}

[JsonProperty("response_status")]
public string ResponseStatus{get; set;}

}

Deserialize JSON Date to C# Object with DateTime Returning 01/01/0001

Add [JsonProperty("Start Date")] above public DateTime StartDate { get; set; } and similar above public DateTime EndDate { get; set; }:

public class MyObject
{
public string Name { get; set; }
[JsonProperty("Start Date")]
public DateTime StartDate { get; set; }
[JsonProperty("End Date")]
public DateTime EndDate { get; set; }
}

The problem was that json property names ('Start Date') and class property names ('StartDate') were not the same so it defaulted to 01/01/0001.

This will work without the need for IsoDateTimeConverter.



Related Topics



Leave a reply



Submit