deserialize a dynamic json object to a class - c#

I'm working with an external API to get some product information, the end points return some data in a static structure and others in dynamic depending on the product I'm inquiring.
For example if I'm requesting data for a soap I get the following JSON:
{ "id": 4623,
"brand": "Fa",
"category": "Cleansing/Washing/Soap – Body",
"photos": {
"name": "Photos",
"value": [ "https//test.com/1jpg"
]
},
"productname": {
"name": "Product Name",
"value": "Fa Shower Cream Yoghurt Vanilla Honey"
},
"warningstatement": {
"name": "Warning Statement",
"value": "Avoid contact with eyes."
},
"consumerusageinstructions": {
"name": "Consumer Usage Instructions",
"value": "Apply directly on skin."
}
and if I'm inquiring about a cheese I get the following JSON:
{
"id": 10838,
"brand": "Domty",
"category": "Cheese",
"photos": {
"name": "Photos",
"value": [ "https://test.com/2.jpg"
]
},
"productname": {
"name": "Product Name",
"value": "Domty White Low Salt Cheese"
},
"description": {
"name": "1312",
"value": "Highest premium quality"
},
"netcontent": {
"name": "Net Content",
"value": "900 gm"
}
and it goes on for every product they offer. I've no problem deserializing the static data like photos array, product name, brand, and id since they are applicable to every product, but the other dynamic properties are the ones I'm concerned about. Is there a way to deserialize to a class like this:
public class Info {
property string key { get; set;} // to hold description, consumerusageinstructions or what every key
property string name { get; set;}
property string value { get; set;}
}
and then add a collection of the class info to my product model?

One way is just to parse the Json and look at the actual entities: this example uses Json.Net:
var parsed = JObject.Parse(json);
var properties = parsed.Children().Cast<JProperty>();
foreach (var property in properties) {
// an alternative here would be to just have a list of names to ignore
if (!(property.Value is JObject jObject)) {
// skip the simple property/value pairs
continue;
}
if (property.Name == "productname") {
// skip product name
continue;
}
if (property.Value["value"] is JArray) {
// skip photos
continue;
}
// Add to ProductModel instance
Console.WriteLine($"{property.Name} => {property.Value["name"]} = {property.Value["value"]}");
}
Outputs:
warningstatement => Warning Statement = Avoid contact with eyes.
consumerusageinstructions => Consumer Usage Instructions = Apply directly on skin.
description => 1312 = Highest premium quality
netcontent => Net Content = 900 gm

Related

Selected JSON data to c# object [duplicate]

This question already has answers here:
How can I deserialize JSON with C#?
(19 answers)
Closed 8 months ago.
{
"name": "India",
"topLevelDomain": [".in"],
"alpha2Code": "IN",
"alpha3Code": "IND",
"callingCodes": ["91"],
"capital": "New Delhi",
"altSpellings": ["IN", "Bhārat", "Republic of India", "Bharat Ganrajya"],
"subregion": "Southern Asia",
"region": "Asia",
"population": 1380004385,
"latlng": [20.0, 77.0],
"demonym": "Indian",
"area": 3287590.0,
"gini": 35.7,
"timezones": ["UTC+05:30"],
"borders": ["AFG", "BGD", "BTN", "MMR", "CHN", "NPL", "PAK", "LKA"],
"nativeName": "भारत",
"numericCode": "356",
"flags": {
"svg": "https://flagcdn.com/in.svg",
"png": "https://flagcdn.com/w320/in.png"
},
"currencies": [{ "code": "INR", "name": "Indian rupee", "symbol": "₹" }],
"languages": [
{
"iso639_1": "hi",
"iso639_2": "hin",
"name": "Hindi",
"nativeName": "हिन्दी"
},
{
"iso639_1": "en",
"iso639_2": "eng",
"name": "English",
"nativeName": "English"
}
],
"translations": {
"br": "Índia",
"pt": "Índia",
"nl": "India",
"hr": "Indija",
"fa": "هند",
"de": "Indien",
"es": "India",
"fr": "Inde",
"ja": "インド",
"it": "India",
"hu": "India"
},
"flag": "https://flagcdn.com/in.svg",
"regionalBlocs": [
{
"acronym": "SAARC",
"name": "South Asian Association for Regional Cooperation"
}
],
"cioc": "IND",
"independent": true
}
This is my JSON data.From this I need to convert
name
population
area
altSpellings
these values to c#
Using the Newtonsoft.Json library you can load the JSON content into a JObject and treat it like a dictionary where the property names are the key values like so:
//Load the JSON into a JObject
var json = ...
var jsonObject = JObject.Parse(json);
//Pull out the property values using the name as a key
var name = jsonObject["name"].ToString();
var population = int.Parse(jsonObject["population"].ToString());
var area = jsonObject["area"].ToString();
var altSpellings = new List<string>();
foreach (var altSpelling in (JArray)jsonObject["altSpellings"])
altSpellings.Add(altSpelling.ToString());
This is a quick way to get the values out without having to create concrete types to deserialise into. If you happen to have a class that maps onto this JSON structure (json2csharp can be handy here) then you could instead use JsonConvert.DeserializeObject<T> instead to deserialise into an instance of that type where you can access the fields directly.
you can create a class with the properties you need
using Newtonsoft.Json;
Data data = JsonConvert.DeserializeObject<Data>(json);
string name = data.Name; // India
result
{
"name": "India",
"population": 1380004385,
"area": 3287590,
"altSpellings": [
"IN",
"Bhārat",
"Republic of India",
"Bharat Ganrajya"
]
}
class
public partial class Data
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("population")]
public long Population { get; set; }
[JsonProperty("area")]
public long Area { get; set; }
[JsonProperty("altSpellings")]
public List<string> AltSpellings { get; set; }
}

How do I make JSON accessible from inside of many arrays?

I hope it doesn't seem like I want to be spoon-fed at this rate of asking questions, but this next question is a bit more difficult and I don't understand how to solve this at all.
{
"results": [
{
"id": "5d914028302b840050acbe62",
"picture": "https://utellyassets9-1.imgix.net/api/Images/4e4d50a0040fd4500193202edbafce6a/Redirect",
"name": "BoJack Horseman",
"locations": [
{
"icon": "https://utellyassets7.imgix.net/locations_icons/utelly/black_new/NetflixIVAUS.png?w=92&auto=compress&app_version=ae3576e2-0796-4eda-b953-80cadc8e2619_eww2020-05-08",
"display_name": "Netflix",
"name": "NetflixIVAUS",
"id": "5d81fe2fd51bef0f42268f0f",
"url": "https://www.netflix.com/title/70298933"
}
],
"provider": "iva",
"weight": 5654,
"external_ids": {
"iva_rating": null,
"imdb": {
"url": "https://www.imdb.com/title/tt3398228",
"id": "tt3398228"
},
"tmdb": {
"url": "https://www.themoviedb.org/movie/61222",
"id": "61222"
},
"wiki_data": {
"url": "https://www.wikidata.org/wiki/Q17733404",
"id": "Q17733404"
},
"iva": {
"id": "783721"
},
"gracenote": null,
"rotten_tomatoes": null,
"facebook": null
}
},
{
"id": "5e2ce07890c0e033a487e3d2",
"picture": "https://utellyassets9-1.imgix.net/api/Images/326d2853ff6885c41b9adb05278017f6/Redirect",
"name": "Dragon Ball Z: Bojack Unbound",
"locations": [
{
"icon": "https://utellyassets7.imgix.net/locations_icons/utelly/black_new/iTunesIVAUS.png?w=92&auto=compress&app_version=ae3576e2-0796-4eda-b953-80cadc8e2619_eww2020-05-08",
"display_name": "iTunes",
"name": "iTunesIVAUS",
"id": "5d80a9a5d51bef861d3740d3",
"url": "https://itunes.apple.com/us/movie/dragon-ball-z-bojack-unbound-subtitled-original-version/id1381102560"
}
],
"provider": "iva",
"weight": 0,
"external_ids": {
"iva_rating": null,
"imdb": {
"url": "https://www.imdb.com/title/tt0142238",
"id": "tt0142238"
},
"tmdb": {
"url": "https://www.themoviedb.org/movie/39105",
"id": "39105"
},
"wiki_data": {
"url": "https://www.wikidata.org/wiki/Q1255010",
"id": "Q1255010"
},
"iva": {
"id": "406043"
},
"gracenote": null,
"rotten_tomatoes": null,
"facebook": null
}
}
],
"updated": "2020-05-08T05:19:01+0100",
"term": "Bojack",
"status_code": 200,
"variant": "ivafull"
}
Alright, so the first 0: represents the option that the API returned (so in this case it's bojack horseman) and any following number (if it said 1: afterward) would be a different result.
I tried writing a second class to deal with results (resultoverall) that is controlled by the overall,
public class overall
{
[JsonProperty("status_code")]
public string status_code { get; set; }
[JsonProperty("term")]
public string term { get; set; }
[JsonProperty("updated")]
public string updated { get; set; }
[JsonProperty("results")]
public List<resultoverall> results { get; set; }
}
public partial class resultoverall
{
[JsonProperty("name")]
public string name { get; set; }
}
and tried
overall BestGamer = JsonConvert.DeserializeObject<overall>(thicky);
List<overall> ObjOrderList = JsonConvert.DeserializeObject<List<overall>>(thicky);
to be able to access data from both outside of the 0: and inside of the 0: (BestGamer / overall handles outside, resultoverall / Tapioca handles inside, but I get error about the type requiring a JSON array
even though it is already in an array format.
How do I set this up to access data inside of the 0:, for each number that appears (so if there is 2 options I can access info from both?
Using Console.WriteLine(BestGamer.updated); correctly gives me the info from the overall group, but using the Console.WriteLine(BestGamer.results.name); does not work, saying:
Error CS1061 'extension method 'name' accepting a first argument of type 'List<resultoverall>' could be found (are you missing a using directive or an assembly reference?)
You have a JSON object, not an array. Therefore you can't deserialize it as an array. That is correct. You are already deserializing it to an object. So after
overall BestGamer = JsonConvert.DeserializeObject<overall>(thicky);
You can access the objects in results like
var numberOfResults = BestGamer.results.Count;
var firstResult = BestGamer.results[0];
var firstResultsName = BestGamer.results[0].name;
// or loop through the results
foreach (var item in BestGamer.results)
{
// item.Name gives you the name of the current result
}

Adding multiple properties into nested JSON array

I've done a lot of searching at it appears there is no article for this specific case.
I have some JSON (Note that it is a JArray, and not a JObject)
[
{
"Name": "Name 1",
"Icon": "Icon 1",
"Samples": [
{
"Name": "Sample 1",
},
{
"Name": "Sample 2",
},
{
"Name": "Sample 3",
}
]
},
{
"Name": "Name 2",
"Icon": "Icon 2",
"Samples": [
{
"Name": "Sample 1",
},
{
"Name": "Sample 2",
},
{
"Name": "Sample 3",
}
]
},
{
"Name": "Name 3",
"Icon": "Icon 3",
"Samples": [
{
"Name": "Sample 1",
},
{
"Name": "Sample 2",
},
{
"Name": "Sample 3",
}
]
},
]
So this has 3 levels. The root, which contains the "SampleCategories". The "SampleCategories" which contains some items and a "Samples" list, and the "Samples" list which contains many objects.
So, what I'm essentially trying to do here is to add an object into the Samples list, inside a specific SampleCategory. So for example:
I want to add "Sample 4" to the Samples array in "Name 1" so it looks like this:
{
"Name": "Name 1",
"Icon": "Icon 1",
"Samples": [
{
"Name": "Sample 1",
},
{
"Name": "Sample 2",
},
{
"Name": "Sample 3",
},
{
"Name": "Sample 4",
}
]
},
I thought this would be fairly easy when I started out but it turns out there are lots of roadblocks that I can't seem to get my head around.
So I have two models for each level, "SampleCategory" for the top level that has the Name, Icon and Samples, which goes off to another model called "Samples[]"
So I'm afraid I'm at an embarrassing point in this process... Right at the start. I've de-serialised the json, and done some bits around finding my chosen SampleCategory, and created an object ready to add in, and that's as far as I've gotten...
var categories = JsonConvert.DeserializeObject<List<SampleCategory>>(json);
var applications = categories.Find(c => c.Name.Equals("Name 1"));
var sample = new Sample
{
Name = "Sample 4",
};
I don't appear to be able to find any way of adding a new 'sample' into the Samples list of a chosen SampleCategory. I even tried adding to the object path directly, the path of which I got using this:
JObject jo = jsonArray.Children<JObject>().FirstOrDefault(o => o["Name"] != null && o["Name"].ToString() == "Name 1");
But there doesn't appear to be a way to add to an array as I guess it's a static length?
What is best practice modifying JSON like this?
Documentation (https://www.newtonsoft.com/json/help/html/ModifyJson.htm) suggests there a few ways to do it when playing around with JObjects, but it doesn't suggest a way to do it with nested objects inside of JArrays.
Can anyone help or suggest some methods to do this? I'm sure it's probably simple, but I'm going through a loop trying to figure it out.
I guess your model looks something like this:
public class Sample
{
public string Name { get; set; }
}
public class SampleCategory
{
public string Name { get; set; }
public string Icon { get; set; }
public List<Sample> Samples { get; set; }
}
If you are not sure how a model must look like to match a given json string you can make use of a tool like json2csharp which will generate the model for you.
To be able to add a new sample to a specific category you can find the specific category using LINQ after deserializing it:
var categories = JsonConvert.DeserializeObject<List<SampleCategory>>(json);
var category = categories.FirstOrDefault(c => c.Name == "Name 1");
Now (if a category with the name exists) just add the new sample to the list:
if(category != null)
{
category.Samples.Add(new Sample{Name = "Sample 4"});
}
To serialize it back to json:
var json = JsonConvert.SerializeObject(categories);
Edit
If you cannot switch your model type for Samples to List<T> and you don't want to convert the array temporarily to List<T>, you can do something like this:
if(category != null)
{
category.Samples = category.Samples.Concat(new Sample[]{new Sample{Name = "Sample 4"}}).ToArray();
}
If you are not bothered by an additional List<T> cast another approach would be:
if(category != null)
{
var tmpList = category.Samples.ToList();
tmpList.Add(new Sample{Name = "Sample 4"});
category.Samples = tmpList.ToArray();
}
I'm not sure but it could be as simple as :
applications.Samples.ToList().Add(sample);
I have no privilege to comment it :(

JSON Get List of attributes values

I have the following JSON, and I want to take a list of IDs with its values
For example in LINQ something like this: result.Select(x=>x.id)
I tried this:
var inner = outer["pictures"].Value<JArray>().ToList();
I have a list there but with all attributes but I am not able to select just IDs since it is an anonymous list.
outer
{{
"id": "669654603",
"pictures": [
{
"id": "659745-MLA25600661898_052017",
"url": "http://mla-s2-p.mlstatic.com/659745-MLA25600661898_052017-O.jpg",
"secure_url": "https://mla-s2-p.mlstatic.com/659745-MLA25600661898_052017-O.jpg",
"size": "500x365",
"max_size": "625x457",
"quality": ""
},
{
"id": "908422-MLA25658267858_062017",
"url": "http://mla-s2-p.mlstatic.com/908422-MLA25658267858_062017-O.jpg",
"secure_url": "https://mla-s2-p.mlstatic.com/908422-MLA25658267858_062017-O.jpg",
"size": "47x47",
"max_size": "47x47",
"quality": ""
},
{
"id": "794138-MLA25658267889_062017",
"url": "http://mla-s2-p.mlstatic.com/794138-MLA25658267889_062017-O.jpg",
"secure_url": "https://mla-s2-p.mlstatic.com/794138-MLA25658267889_062017-O.jpg",
"size": "40x40",
"max_size": "40x40",
"quality": ""
}
]
}}
When working with Newtonsoft JSON you can do it like this:
var values = JObject.Parse(jsonString)["pictures"].Select(p => p["id"].Value<string>()).ToList();
In order to be valid your json should not contain double curly brackets:
{
"id": "669654603",
"pictures": [ ... ]
}
You can parse it without additional classes:
var ids = JObject.Parse(json)["pictures"].Select(p => (string)p["id"]);
Output:
[
"659745-MLA25600661898_052017",
"908422-MLA25658267858_062017",
"794138-MLA25658267889_062017"
]
Or you can create several classes to hold your json data:
public class Container
{
public List<Picture> Pictures { get; set; }
}
public class Picture
{
public string Id { get; set; }
// you can add other properties here
}
And use strongly-typed parsing:
var ids = JsonConvert.DeserializeObject<Container>(json).Pictures.Select(p => p.Id);

Search for a nested value inside of a JSON.net object in C#

I've got a JSON stream coming back from a server, and I need to search for a specific value of the node "ID" using JSON.net to parse the data.
And I can almost make it work, but not quite because the results coming back are deeply nested in each other -- this is due to the fact that I'm getting a folder structure back. I've boiled the JSON down to a much simpler version. I'm getting this:
{
"data": {
"id": 0,
"name": "",
"childFolders": [{
"id": 19002,
"name": "Locker",
"childFolders": [{
"id": 19003,
"name": "Folder1",
"childFolders": [],
"childComponents": [{
"id": 19005,
"name": "route1",
"state": "STOPPED",
"type": "ROUTE"
}]
}, {
"id": 19004,
"name": "Folder2",
"childFolders": [],
"childComponents": [{
"id": 19008,
"name": "comm1",
"state": "STOPPED",
"type": "COMMUNICATION_POINT"
}, {
"id": 19006,
"name": "route2",
"state": "STOPPED",
"type": "ROUTE"
}, {
"id": 19007,
"name": "route3",
"state": "STOPPED",
"type": "ROUTE"
}]
}],
"childComponents": []
}],
"childComponents": []
},
"error": null
}
I can almost get there by going:
var objects = JObject.Parse(results);
var subobjects = objects["data"]["childFolders"][0]["childFolders"][1];
I can see in the debug view that it'll parse the object, but won't let me search within.
My ultimate goal is to be able to search for "route3" and get back 19007, since that's the ID for that route. I've found some results, but all of them assume you know how far nested the object is. The object I'm searching for could be 2 deep or 20 deep.
My ultimate goal is to be able to search for "route3" and get back 19007
You can use linq and Descendants method of JObject to do it:
var dirs = JObject.Parse(json)
.Descendants()
.Where(x=>x is JObject)
.Where(x=>x["id"]!=null && x["name"]!=null)
.Select(x =>new { ID= (int)x["id"], Name = (string)x["name"] })
.ToList();
var id = dirs.Find(x => x.Name == "route3").ID;
You can use the SelectToken or SelectTokens functions to provide a JPath to search for your desired node. Here is an example that would provide you the route based on name:
JObject.Parse(jsonData)["data"].SelectToken("$..childComponents[?(#.name=='route3')]")
You can find more documentation on JPath here
Simply write a recursive function:
private Thing FindThing(Thing thing, string name)
{
if (thing.name == name)
return thing;
foreach (var subThing in thing.childFolders.Concat(thing.childComponents))
{
var foundSub = FindThing(subThing, name);
if (foundSub != null)
return foundSub;
}
return null;
}
class RootObject
{
public Thing data { get; set; }
}
class Thing
{
public int id { get; set; }
public string name { get; set; }
public List<Thing> childFolders { get; set; } = new List<Thing>();
public List<Thing> childComponents { get; set; } = new List<Thing>();
}
And using it:
var obj = JsonConvert.DeserializeObject<RootObject>(jsonString);
var result = FindThing(obj.data, "route3");

Categories

Resources