Parsing JSON and applying filter - c#

I've ObservableCollection<dynamic> called myJSON in C# having 4 items as follows:
[
{
"name": "A",
"location": "NY"
},
{
"name": "B",
"location": "NJ"
},
{
"name": "A",
"location": "NY"
},
{
"name": "D",
"location": "MA"
}
]
I need to be able to apply a filter query like say where name="A" and location="NY" and then get back 2 records from above.
I tried code like below but I was only able to parse one record at a time from the above collection. And also the 2nd line seems to error out with a message:
"Cannot access child value on Newtonsoft.Json.Linq.JValue."
JObject json = JObject.Parse(myJSON[0].ToString());
var match = json.Values<JProperty>().Where(m => m.Value["name"].ToString() == "A" && m.Value["location"].ToString() == "NY").FirstOrDefault();
Thanks.

Here:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace NameLoc
{
class Program
{
class NameLoc
{
public string Name;
public string Location;
}
static void Main(string[] args)
{
var lst = JsonConvert.DeserializeObject<ObservableCollection<NameLoc>>(File.ReadAllText("NameLoc.json"));
var selLst = from sel in lst where sel.Name == "A" && sel.Location == "NY" select sel;
foreach (var it in selLst)
{
Console.WriteLine($"{it.Name}, {it.Location}");
}
Console.WriteLine("Hello World!");
}
}
}
And then be sure to add a file "NameLoc.Json" Set build Action Copy To output I found errors in your json corrected below. Add Newtonsoft.json to pkgs
[
{
"name": "A",
"location": "NY"
},
{
"name": "B",
"location": "NJ"
},
{
"name": "A",
"location": "NY"
},
{
"name": "D",
"location": "MA"
}
]

First, let's fix json format. If you are talking about collection or array then your json format should be like this:
[
{
"name": "A",
"location": "NY"
},
{
"name": "B",
"location": "NJ"
},
{
"name": "A",
"location": "NY"
},
{
"name": "D",
"location": "MA"
}
]
And you should use JArray instead of JObject to parse it so that you can apply filter.
JArray jsonArray = JArray.Parse(jsonString);
var match = jsonArray.Where(i => i["name"].ToString() == "A" && i["location"].ToString() == "NY").ToList();

You do know that your json array is incorrect, don't you? An array is within square brackets [ ... ]. I assume that is a typing error.
My advice would be to separate your concerns: split your problem into separate parts:
I have a string in JSON format containing information about Persons (?), and I need to convert this into a sequence of Persons
I have a sequence of Persons, I need only those Persons with a certain Name and Location.
If you do this, your code will be easier to understand, more reusable, easier to test and easier to change. Who does not want that?
Convert string to a sequence of Persons
For this I write an extension function. This way it looks more like LINQ. See extension methods demystified
public class Person
{
public string Name {get; set;}
public string Location {get; set;}
... // other properties
}
// Converts a JToken to a Person:
public static Person ToPerson(this JToken token)
{
// TODO: decide what to do if token == null. Return null?
Person person = token.ToObject<Person>();
return person;
}
// Converts JSON string to sequence of Persons
public static IEnumerable<Person> ToPersons(this string jsonText)
{
// TODO: exception if jsonText null, or incorrect
IEnumerable<JToken> jsonArray = JArray.Parse(jsonString);
foreach (JToken token in jsonArray)
{
Person person = token.ToPerson();
yield return person;
}
}
Usage looks like LINQ ToList():
string jsonText = ...
IEnumerable<Person> persons = jsonText.ToPersons();
Keep only Persons with certain name and location
After you've converted the json to a sequence of Persons, the rest is standard LINQ
const int name = "John Doe";
const int location = "New York City";
string jsonText = ...
var result = jsonText.ToPersons()
.Where(person => person.Name == name && person.Location == location);
Advantages of this separation of concerns
This Separation has several advantages:
You can reuse ToPersons() for other LINQ statements: "give me all elderly Persons", "give me the cities where my Persons live".
It is easier to test: just create an array of test persons as a source for your LINQ statements
It is easier to understand: people who read your LINQ statements don't have to bother anymore about the conversion: they know the conversion is used already in a lot of other places, and the test software succeeds, hence you can trust the conversion.
It is easier to change: if you want to add a PostCode to your Persons, go ahead, your LINQ statements won't change, only ToPerson()

Related

deserialize a dynamic json object to a class

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

Dynamically retrieve variable and argument values for NativeActivity

When I run the below code (C#) as a custom activity (compiled .dll is added to a nuget package and triggered in a UiPath sequence with some user defined variables/arguments. I am able to retrieve the name of the variable and it's type, but I cannot find the proper syntax to retrieve the value (I just want to convert it to a String, no need to do anything fancy). I can access some of the properties, so i know i am close. I have done my best to read the docs, but in this instance, it may be a little two abstract for me. I have gone through many interations and scoured as much of the internet as I can, but I cannot seem to figure it out.
using Newtonsoft.Json.Linq;
using System;
using System.Activities;
using System.Activities.Hosting;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
namespace AutoLog
{
public sealed class GetRootActivity : NativeActivity
{
public OutArgument<string> variables { get; set; }
protected override void Execute(NativeActivityContext context)
{
this.variables.Set((ActivityContext)context, Library.getLocalVariables(context));
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
metadata.AddDefaultExtensionProvider<GetRootActivity.WorkflowInstanceInfo>((Func<GetRootActivity.WorkflowInstanceInfo>)(() => new GetRootActivity.WorkflowInstanceInfo()));
}
public class Library
{
public static string getLocalVariables(NativeActivityContext context)
{
var properties = context.DataContext.GetProperties();
JArray variables = new JArray();
foreach(PropertyDescriptor p in properties)
{
JObject variable = new JObject();
variable["name"] = p.Name;
variable["type"] = p.PropertyType.ToString();
string string_value = "";
try
{
var myValue = context.DataContext.GetType().GetProperty(p.Name).GetValue(context.DataContext, null);
string_value = myValue.ToString();
}
catch(Exception e)
{
}
variable["value"] = string_value;
variables.Add(variable);
}
return variables.ToString();
}
}
}
}
Below is an example of the JSON it generates, as you can see, the "value" field is empty
[
{
"name": "a",
"type": "System.String",
"value": ""
},
{
"name": "b",
"type": "System.Boolean",
"value": ""
},
{
"name": "c",
"type": "System.Int32",
"value": ""
},
{
"name": "test",
"type": "System.String",
"value": ""
},
{
"name": "f",
"type": "System.String",
"value": ""
}
]
var myValue = context.DataContext.GetType().GetProperty(p.Name).GetValue(context.DataContext, null);
string_value = myValue.ToString();
can be changed to
string_value = p.GetValue(context.DataContext) as String;
I had previously tried this approach but got trying to make the cast dynamic, and apparently I had never tried the simpler solution.

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