Querying JSON with SelectTokens - c#

I want to query a JArray and get back another JArray based on some conditions. Now using LINQ I can first query it, return an IEnumerable<JToken> and convert it to another JArray like this:
IEnumerable<JToken> ienmTotalObjects = arrResults.Where(x => x["uResultId"]?.ToString() == arrTaskResults[intResult]["uResultId"].ToString() && x["iElementId"]?.ToString() == strUniqueElementId);
JArray arrTotalObjects = new JArray(ienmTotalObject);
Now I just came to know about the JSON.NET SelectTokens(https://www.newtonsoft.com/json/help/html/SelectToken.htm) and seems like a pretty handy feature to query without converting to IEnumerable, However I'm unable to find a way to apply it in my case scenarios. Curious if it it is really possible ? Any help is appreciated.

From the question I can guess the 2 strcutures of arrResults and arrTaskResults
var arrResults = JArray.Parse(#"[
{ iElementId: 1, ""uResultId"" :""aa"" },
{ iElementId: 2, ""uResultId"" :""bb"" }
]");
var arrTaskResults = JArray.Parse(#"[
{ ""uResultId"" :""aa"" },
{ ""uResultId"" :""bb"" }
]");
However, I have no idea what intResult and strUniqueElementId are so these are set here
var intResult = 0;
var strUniqueElementId = "1";
We can now do the same queries but using SelectToken method passing in a JPath:
var s = arrTaskResults.SelectToken($"$[{intResult}].uResultId");
var selectTokens = arrResults.SelectTokens($"$[?(#.uResultId=='{s}' && #.iElementId=={strUniqueElementId})]");
Executing this will output the following:
[
{
"iElementId": 1,
"uResultId": "aa"
}
]

Related

How to sort dynamically in mongodb

I have a "sort" query parameter. This maybe looking like this:
?sort=-name,+number
Now I want to read this and build a sort query for the c# MongoDB-Driver.
I thought of something like this:
var sortByList = parameters.Sort?.Split(',');
foreach (var sortBy in sortByList)
{
if (sortBy.StartsWith('-'))
// add to sort query decending ?!?
else
// add to sort query ascending ?!?
}
The only way I found so far is to build a string like "{name:-1, number:1}" and give this to the .Sort-method. But so I have build to build many strings that I really don't need.
Isn't there a way to do something like:
var sortDefinition = new SortDefintion();
var sortByList = parameters.Sort?.Split(',');
foreach (var sortBy in sortByList)
{
if (sortBy.StartsWith('-'))
sortDefinition.Add(sortBy.Substring(1), -1);
else
sortDefinition.Add(sortBy.Substring(1), 1);
}
return db.GetCollection<BsonDocument>("sortTest").Sort(sortDefintion).ToList();
You can solve it like this:
var bldr = Builders<BsonDocument>.Sort;
var sortDefinitions = sortByList.Select(x =>
{
SortDefinition<BsonDocument> sortDef;
var propName = x.Substring(1);
if (x.StartsWith("-"))
sortDef = bldr.Descending(propName);
else
sortDef = bldr.Ascending(propName);
return sortDef;
});
var sortDef = bldr.Combine(sortDefinitions);
return db
.GetCollection<BsonDocument>("sortTest")
.Find(Builders<BsonDocument>.Filter.Empty)
.Sort(sortDef)
.ToList();
Above code uses a Linq Select to create a SortDefinition<BsonDocument> for each string in the list and combines these definitions into a single sort definition afterwards. This sort definition is then applied to the Find.

How to deserailise JSON Object in C# Without knowing structure

Is it possible to take a JSON object in C# that I read from from another source and convert the contents to files. The problem is I don't know the structure of the incoming objects.
So far I've got this:
var response = await client.GetAsync("https://blahblah/creds" +
"?ApiKey=" + textBox_password.Text +
"&deviceId=" + deviceId +
"&modelNum=" + modelNum);
var res = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var resJson = JsonConvert.DeserializeObject(res);
this.textBox_Stream.AppendText("responseBody = " + resJson + "\r\n");
Which gives me:
responseBody = {
"statusCode": 200,
"body": {
"modelNum": "42",
"creds": "[{\"deviceid.txt\":\"4DA23E\",\"pac.txt\":\"580795498743\"}]",
"deviceId": "4DA23E"
}
}
What I want to do is create a folder called 4DA23E and place one file inside it for each entry in the creds object.
device.txt will contain 4DA23E
pac.tct will contain 580795498743
etc. I can't figure out a dynamic way to extract the stuff I need. Goes without saying, I am not a C# programmer! So, please be kind.
Don't use JsonConvert.DeserializeObject. That's best when you know the schema of the JSON object and you've made a corresponding C# model.
First, parse the outer response JSON
var res = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
JObject resJson = JObject.Parse(res);
Now pull out the value of the "creds" property and re-parse it.
var credsJsonRaw = resJson["body"]["creds"].Value<string>();
JArray credsJson = JArray.Parse(credsJsonRaw);
Now you can loop through each object in the array, and each property in each object.
foreach (JObject obj in credsJson)
{
foreach (JProperty prop in obj.Properties)
{
Console.WriteLine($"Name = {prop.Name}");
Console.WriteLine($"Value = {prop.Value.Value<string>()}");
}
}
For your example, this prints
Name = deviceid.txt
Value = 4DA23E
Name = pac.txt
Value = 580795498743
One way to deserialise the following json is in two stages. This is because 'creds' is a string.
{
"statusCode": 200,
"body": {
"modelNum": "42",
"creds": "[{\"deviceid.txt\":\"4DA23E\",\"pac.txt\":\"580795498743\"}]",
"deviceId": "4DA23E"
}
}
Here's an example of how to do it. If you're not .NET6 change 'record' to 'class' and if you don't use nullable reference types (on by default in .NET6) remove the appropriate question marks in property definitions.
using System.Text.Json;
using System.Text.Json.Serialization;
var json = File.ReadAllText("data.json");
var o = JsonSerializer.Deserialize<Outer>(json);
if(o?.Body?.Creds == null)
{
throw new Exception("Bad data");
}
var credList = JsonSerializer.Deserialize<List<Dictionary<string, string>>>(o.Body.Creds);
if(credList == null)
{
throw new Exception("Bad data");
}
foreach( var e in credList)
foreach( var kvp in e)
Console.WriteLine($"{kvp.Key} : {kvp.Value}");
record Outer {
[JsonPropertyName("staticCode")]
public int? StaticCode {get; set;}
[JsonPropertyName("body")]
public Body? Body {get; set;}
}
record Body {
[JsonPropertyName("creds")]
public string? Creds {get; set;}
}
This prints
deviceid.txt : 4DA23E
pac.txt : 580795498743

There is difference between Return : and Result: in JSON Array

i am getting json result from third party application for integration , it return the value as below. how i can get below json to dataset
{
"Return": {
"InvoiceDetails": {
"Invoiced": "20180930",
"InvoiceID": "",
"Amount": "0.00 "
}
Below is the c# code.
using (var streamInvoiceReader = new StreamReader(httpinvoiceResponse.GetResponseStream()))
{
var responseInv = streamInvoiceReader.ReadToEnd();
DataSet SetInvoice = JObject.Parse(responseInv)["InvoiceDetails"].ToObject<DataSet>();
if (SetInvoice != null && SetInvoice.Tables.Count > 0)
{
grvInvoice.DataSource = SetInvoice.Tables[0];
}
}
In responseInv i can see the return string and it is correct but not able to get it in dataset. there is any difference from result to return? How i can get the data from return array?
thanks in advance.
to solve this problem you should attention to first part of the JSON by [0]
DataSet SetInvoice = JObject.Parse(responseInv)[0]["InvoiceDetails"].ToObject<DataSet>();
Above question i got it working as different way, what i did is :
using (var streamInvoiceReader = new StreamReader(httpinvoiceResponse.GetResponseStream()))
{
var responseInv = streamInvoiceReader.ReadToEnd();
dynamic ResultInvMain = JsonConvert.DeserializeObject(responseInv );
datetime Invoiced = ResultInvMain .Return.InvoiceDetails.Invoiced;
string InvID = ResultInvMain .Return.InvoiceDetails.InvoiceID;
}
and work around with the returned data.

Breeze work-around for multi valued property queries

I'm trying to assemble ad-hoc queries to Breeze.
I have a physician.contact.addresses relationship.
When I try:
myPred = new pred('contact.addresses.street1', op.StartsWith, "a");
And execute it I get:
"The parent value for a property access of a property 'Addresses' is not a single value. Property access can only be applied to a single value."
To try a work-around I've tried parsing out those many-relationships and am passing it to the breeze controller in .withParameters like this:
var criteriaStr = toManyArray.length ? ko.utils.stringifyJson(toManyArray) : "";
query = query.withParameters({ searchParms: criteriaStr });
where toManyArray is an array of fieldName:value pairs.
on the controller side:
[HttpGet]
public IQueryable<Physician> Physician(string searchParms = null)
{
if (searchParms != null)
{
var ser = new JavaScriptSerializer();
var searchCritAry = ser.Deserialize<String[]>(searchParms);
foreach (var aryItem in searchCritAry)
{
// aryItem looks like this:
// f_str_street_from_addresses:a
var predEnt = aryItem.Split(':')[0].Split('_')[4];
var predField = aryItem.Split(':')[0].Split('_')[2];
var predVal = aryItem.Split(':')[1];
switch (predEnt)
{
case "addresses":
switch (predField)
{
case "street":
//physPool =
_contextProvider.Context.Physicians
.Where(p => p.Contact.Addresses.Any(a => a.Street1.StartsWith(predVal)));
break;
case "street2":
//physPool =
_contextProvider.Context.Physicians
.Where(p => p.Contact.Addresses.Any(a => a.Street2.StartsWith(predVal)));
break;
}
break;
}
}
// here I want to combine the .Where clauses from above with the breeze preds
return _contextProvider.Context.Physicians;
}
return _contextProvider.Context.Physicians;
}
It's not working and only returning a selection using the predicates that are passed in the normal way through Breeze's query. I don't see how to pass the filtered IQueryable to Breeze's _contextProvider.
Thanks for any suggestions.
Take a look at the new "any/all" support in Breeze ( as of version 1.4.7). Some examples may be found here: http://www.breezejs.com/documentation/query-examples
You can construct your query/predicate like this:
var pred = new Breeze.Predicate('contact.addresses', "any", 'street1', op.StartsWith, "a");
var query = EntityQuery.from("Physicians").where(pred);
or simply
var query = EntityQuery.from("Physicians")
.where("contact.addresses", "any", "street1", "startsWith", 'a')
You may want to add an expand if you want the contact and address information sent down as well.
query = query.expand("contact.addresses");
If you have a need for nested any/all expressions you may need to add the following configuration to your BreezeController to determine just how deep you want to allow the expressions to go (2 in the example below):
[BreezeController(MaxAnyAllExpressionDepth = 2)]

Use a numeric value in a linq dynamic query string

I am trying to make a dynamic linq query that will check for values based on a string.
First of all, here's the query:
objQry = from o in m_Db.OBJECTS.Where(whereConditions)
select o;
if(!objQry.Any())
{
return null;
}
The whereConditions variable is a string I build and pass as parameter to find out the values I need. Here's examples of valid string:
OBJ_NAME == \"Sword\" and OBJ_OWNER == \"Stan\"
This will return any item whose name is "Sword" and owner is "Stan;
OBJ_COLOR == \"Blue\" OR OBJ_COLOR == \"Red\"
This will return any item which color is either blue or red.
Up to there, I'm fine, but now I have a problem: I need to check a decimal field. So I've tried this string:
OBJ_NUMBER == 1
But the query returns null even if there are objects which OBJ_NUMBER value is 1. It's a decimal. How can I indicate the query that they need to check for a decimal value?
**** EDIT ****
I have tried to "modify" the value passed so that it looks like this:
"CARD_NUMBER == Convert.ToDecimal(1)"
And now I have a different kind of error telling me this:
LINQ to Entities does not recognize the method 'System.Decimal ToDecimal(Int32)' method, and this method cannot be translated into a store expression.
Any clues anyone? I'm still looking for a way to do this. Thanks!
EDIT 2
You can get an example of how my code is shaped by looking at this question.
Let's come back at this problem. I want to check decimal values. Let's say that OBJ_NUMBER is a decimal field.
Using Dynamic Linq, I tried to read the decimal field. Say that I want to get each object which number is 1.27. The whereConditions field would then be shaped like this:
OBJ_NUMBER == 1.27
But then I would get an Invalid real literal '1.27' error. I don't know why.
So I have tried Gert Arnold's solution and done this instead:
decimal bDecimal = decimal.Parce(valueToParse);
param = new ObjectParameter("cardNumber", typeof(decimal)) { Value = bDecimal };
valuesToUse.Add("CARD_NUMBER == #cardNumber");
listParams.Add(param);
But I ended up having 2 problems:
The first problem is that my whereConditions string is shaped this way:
CARD_NUMBER == #cardNumber
But I get the following error:
No property or field 'cardNumber' exists in type 'CARD'
Leading me to believe that it cannot make the link between the object parameter and the string used to do the query.
As you can see, I have a list of Params. This is because I cannot know for sure how many parameters the user will chose. So each time the user enters a new search field, I have to create a new ObjectParameter and store it in a list. Here's how I try to do the thing after:
ObjectParameter[] arrayParameters = listParams.ToArray();
// Convert the list to an array
And then, when I try to make the query:
cardQry = from c in mDb.CARD.Where(whereConditions, arrayParameters)
select c;
But to no avail.
RESULTS
Based on the answered question below, I have developped something "awful", yet functional.
First of all, I ignore every decimal fields because I could never reach them with dynamic linq. Instead, I do this:
var valuesToParse = keyValuePair.Value.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
// Here I parse the value and, if that's the case, the symbol.
decimal baseValue = decimal.Parse(valuesToParse[0]);
if (valuesToParse.Count() > 1)
{
string baseMethod = valuesToParse[1];
if (baseMethod == ">" || baseMethod == ">=")
{
if (baseMethod == ">=")
{
baseValue--;
}
// The list is actually like this: Dictionary<string, object> list = new Dictionary<string, object>();
list.Add("low", baseValue);
// I kind of activate a tag telling me that the user is looking for a higher value.
cardHigher = true;
}
else
{
if (baseMethod == "<=")
{
baseValue++;
}
list.Add("low", baseValue);
cardLower = true;
}
}
else
{
//lowParam = new ObjectParameter("dec", typeof(decimal)) { Value = baseValue };
list.Add("low", baseValue);
}
cardNumberActivated = true;
At the end, when I get the list of objects, I do this:
if (list.Count > 0)
{
(example)
if (cardNumberActivated)
{
if (cardHigher)
{
q = mDb.CARD.Where("CARD_NUMBER >= #0", list["low"]).ToList();
}
else if (cardLower)
{
q = mDb.CARD.Where("CARD_NUMBER <= #0", list["low"]).ToList();
}
else
{
q = mDb.CARD.Where("CARD_NUMBER == #0", list["low"]).ToList();
}
}
}
// Here we get the orinalData with the basic filters.
listToReturn.AddRange(cardQry);
if (q != null)
{
//listToReturn.AddRange(q);
for (int i = 0; i < listToReturn.Count; i++)
{
var priceList1 = listToReturn[i];
if (!q.Any(_item => _item.CARD_NUMBER == priceList1.CARD_NUMBER))
{
listToReturn.RemoveAt(i);
i--;
}
}
}
And it works. This is not an elegant way to make it work, but I can validate the fields the way I wanted, and for this, I am thankful at last.
You should not build a query string with inline predicate values. Use parameters in stead. Then will also be able to specify the type:
var whereConditions= "it.CARD_NUMBER = #cardNumber";
var param = new ObjectParameter("cardNumber", typeof(decimal)) { Value = 1 };
objQry = from o in m_Db.OBJECTS.Where(whereConditions, param);
Edit
I don't know what doesn't work in your code. Here's just a random piece of working code derived from one of my own projects:
var param1 = new ObjectParameter("dec", typeof(decimal)) { Value = 90000m };
var param2 = new ObjectParameter("int", typeof(int)) { Value = 90000 };
var q = ValueHolders.Where("it.DecimalValue >= #dec OR it.IntegerValue > #int",
param1, param2).ToList();
Note that param1, param2 could also be an array of ObjectParameter.

Categories

Resources