I am working at parsing a json http response with Json.NET and have working code, but am pretty sure I am going about it in an overly complicated way. My question is if there is a more direct way to get a child jToken by path and/or de-serialize it without foreaching every level.
I tried this approach but it returns null:
JObject jObj = JObject.Parse( text );
JToken myVal;
jObj.TryGetValue( "response.docs", out myVal );
Here is my working overly complicated code, including de-serialization:
JObject jObj = JObject.Parse( text );
foreach( var kv in jObj ) {
if( kv.Key == "response" ) {
foreach( JToken jt in kv.Value ) {
if( jt.Path == "response.docs" ) {
JEnumerable<JToken> children = jt.Children();
foreach( JToken t in children ) {
//THIS WORKS BUT IS NOT ELEGANT
Solr_User[] su = t.ToObject<Solr_User[]>();
}
}
}
}
}
And here is the JSON raw response just for reference:
{
"responseHeader":{
"status":0,
"QTime":0,
"params":{
"q":"*:*",
"indent":"on",
"wt":"json"}},
"response":{"numFound":4,"start":0,"docs":[
{
"id":3,
"first_name":"Bob",
"_version_":"1558902640594649088"},
{
"id":4,
"first_name":"Sam",
"_version_":"1558902640613523456"},
{
"id":2,
"first_name":"Fred",
"_version_":"1558902640613523457"},
{
"id":1,
"first_name":"Max",
"_version_":"1558902640613523458"}]
}}
You can use SelectToken() to select a token from deep within the LINQ-to-JSON hierarchy for deserialization. In two lines:
var token = jObj.SelectToken("response.docs");
var su = token == null ? null : token.ToObject<Solr_User []>();
Or in one line, by conditionally deserializing a null JToken when the selected token is missing:
var su = (jObj.SelectToken("response.docs") ?? JValue.CreateNull()).ToObject<Solr_User []>();
Sample fiddle.
In c# 6 or later it's even easier to deserialize a nested token in one line using the null conditional operator:
var su = jObj.SelectToken("response.docs")?.ToObject<Solr_User []>();
Or even
var su = jObj?["response"]?["docs"]?.ToObject<Solr_User []>();
Note that SelectTokens() is slightly more forgiving than the JToken index operator, as SelectTokens() will return null for a query of the wrong type (e.g. if the value of "response" were a string literal not a nested object) while the index operator will throw an exception.
Related
Simple.OData.Client has a typed and dynamic (and basic) syntax.
I like the typed, but I don't want to build out all my types. In the end I really only need two or so types in the results I get.
But my queries need more types to properly filter the results.
So I want to use the dynamic syntax. But I want to cast the results to classes I have.
I can easily do this manually, but I thought I would see if Simple.OData.Client supports this before I go writing up all that conversion code for each query.
Here is some dynamic syntax code that runs without errors:
client.For(x.Client).Top(10).Select(x.ClientId, x.Name).FindEntriesAsync();
Here is an example of what I had hoped would work (selecting into a new Client object)
client.For(x.Client).Top(10).Select(new Client(x.ClientId, x.Name)).FindEntriesAsync();
But that kind of projection is not supported (I get an "has some invalid arguments" error).
Is there a way to support projection into an existing class when using the dynamic syntax of Simple.OData.Client?
EDIT: The code below works. But it's performance is terrible. I decided to abandon it and write hand written mappers for each type I needed.
This is what I came up with:
dynamic results = oDataClient.For(x.Client).Select(x.ClientId, x.Name).FindEntriesAsync().Result;
var listOfClients = SimpleODataToList<Client>(results);
public List<T> SimpleODataToList<T>(dynamic sourceObjects) where T : new()
{
List<T> targetList = new List<T>();
foreach (var sourceObject in sourceObjects)
{
// This is a dictionary with keys (properties) and values. But no
// matter what sourceObject is passed in, the values will always be
// the values of the first entry in the sourceObjects list.
var sourceProperties = ((System.Collections.Generic.IDictionary<string, object>)sourceObject);
var targetProperties = typeof(Client).GetProperties().Where(prop => prop.CanWrite);
var targetObject = new T();
foreach (var targetProperty in targetProperties)
{
if (sourceProperties.ContainsKey(targetProperty.Name))
{
var sourceValue = GetProperty(sourceObject, targetProperty.Name);
targetProperty.SetValue(targetObject, sourceValue, null);
}
}
targetList.Add(targetObject);
}
return targetList;
}
public static object GetProperty(object o, string member)
{
if (o == null) throw new ArgumentNullException("o");
if (member == null) throw new ArgumentNullException("member");
Type scope = o.GetType();
IDynamicMetaObjectProvider provider = o as IDynamicMetaObjectProvider;
if (provider != null)
{
ParameterExpression param = Expression.Parameter(typeof(object));
DynamicMetaObject mobj = provider.GetMetaObject(param);
GetMemberBinder binder = (GetMemberBinder)Microsoft.CSharp.RuntimeBinder.Binder.GetMember(0, member, scope, new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null) });
DynamicMetaObject ret = mobj.BindGetMember(binder);
BlockExpression final = Expression.Block(
Expression.Label(CallSiteBinder.UpdateLabel),
ret.Expression
);
LambdaExpression lambda = Expression.Lambda(final, param);
Delegate del = lambda.Compile();
return del.DynamicInvoke(o);
}
else
{
return o.GetType().GetProperty(member, BindingFlags.Public | BindingFlags.Instance).GetValue(o, null);
}
}
It was made much harder because normal casts and such for the dynamic objects returned would only give the first object in the list over and over. The GetProperty method works around this limitation.
I have a Category entity which has a Nullable ParentId field. When the method below is executing and the categoryId is null, the result seems null however there are categories which has null ParentId value.
What is the problem in here, what am I missing?
public IEnumerable<ICategory> GetSubCategories(long? categoryId)
{
var subCategories = this.Repository.Categories.Where(c => c.ParentId == categoryId)
.ToList().Cast<ICategory>();
return subCategories;
}
By the way, when I change the condition to (c.ParentId == null), result seems normal.
Other way:
Where object.Equals(c.ParentId, categoryId)
or
Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId)
The first thing to do is to put on logging, to see what TSQL was generated; for example:
ctx.Log = Console.Out;
LINQ-to-SQL seems to treat nulls a little inconsistently (depending on literal vs value):
using(var ctx = new DataClasses2DataContext())
{
ctx.Log = Console.Out;
int? mgr = (int?)null; // redundant int? for comparison...
// 23 rows:
var bosses1 = ctx.Employees.Where(x => x.ReportsTo == (int?)null).ToList();
// 0 rows:
var bosses2 = ctx.Employees.Where(x => x.ReportsTo == mgr).ToList();
}
So all I can suggest is use the top form with nulls!
i.e.
Expression<Func<Category,bool>> predicate;
if(categoryId == null) {
predicate = c=>c.ParentId == null;
} else {
predicate = c=>c.ParentId == categoryId;
}
var subCategories = this.Repository.Categories
.Where(predicate).ToList().Cast<ICategory>();
Update - I got it working "properly" using a custom Expression:
static void Main()
{
ShowEmps(29); // 4 rows
ShowEmps(null); // 23 rows
}
static void ShowEmps(int? manager)
{
using (var ctx = new DataClasses2DataContext())
{
ctx.Log = Console.Out;
var emps = ctx.Employees.Where(x => x.ReportsTo, manager).ToList();
Console.WriteLine(emps.Count);
}
}
static IQueryable<T> Where<T, TValue>(
this IQueryable<T> source,
Expression<Func<T, TValue?>> selector,
TValue? value) where TValue : struct
{
var param = Expression.Parameter(typeof (T), "x");
var member = Expression.Invoke(selector, param);
var body = Expression.Equal(
member, Expression.Constant(value, typeof (TValue?)));
var lambda = Expression.Lambda<Func<T,bool>>(body, param);
return source.Where(lambda);
}
My guess is that it's due to a rather common attribute of DBMS's - Just because two things are both null does not mean they are equal.
To elaborate a bit, try executing these two queries:
SELECT * FROM TABLE WHERE field = NULL
SELECT * FROM TABLE WHERE field IS NULL
The reason for the "IS NULL" construct is that in the DBMS world, NULL != NULL since the meaning of NULL is that the value is undefined. Since NULL means undefined, you can't say that two null values are equal, since by definition you don't know what they are.
When you explicitly check for "field == NULL", LINQ probably converts that to "field IS NULL". But when you use a variable, I'm guessing that LINQ doesn't automatically do that conversion.
Here's an MSDN forum post with more info about this issue.
Looks like a good "cheat" is to change your lambda to look like this:
c => c.ParentId.Equals(categoryId)
You need to use operator Equals:
var subCategories = this.Repository.Categories.Where(c => c.ParentId.Equals(categoryId))
.ToList().Cast<ICategory>();
Equals fot nullable types returns true if:
The HasValue property is false, and the other parameter is null. That is, two null values are equal by definition.
The HasValue property is true, and the value returned by the Value property is equal to the other parameter.
and returns false if:
The HasValue property for the current Nullable structure is true, and the other parameter is null.
The HasValue property for the current Nullable structure is false, and the other parameter is not null.
The HasValue property for the current Nullable structure is true, and the value returned by the Value property is not equal to the other parameter.
More info here Nullable<.T>.Equals Method
Or you can simply use this. It will also translate to a nicer sql query
Where((!categoryId.hasValue && !c.ParentId.HasValue) || c.ParentId == categoryId)
What about something simpler like this?
public IEnumerable<ICategory> GetSubCategories(long? categoryId)
{
var subCategories = this.Repository.Categories.Where(c => (!categoryId.HasValue && c.ParentId == null) || c.ParentId == categoryId)
.ToList().Cast<ICategory>();
return subCategories;
}
Linq to Entities supports Null Coelescing (??) so just convert the null on the fly to a default value.
Where(c => c.ParentId == categoryId ?? 0)
I am querying an xml and i am storing the results using singleordefault
var query = from nm in xelement.Descendants("EmployeeFinance")
where (int)nm.Element("EmpPersonal_Id") == empID
select new AllowancePaid
{
gradeTaxId = nm.Element("Allow-GradeTax").Elements("Amount").Attributes("BenListId").Select(a => (int)a).ToList(),
gradeTaxAmt = nm.Element("Allow-GradeTax").Elements("Amount").Select(a => (double)a).ToList()
};
Debug.WriteLine("2");
var resultquery = query.SingleOrDefault();
now this line: var resultquery = query.SingleOrDefault(); works fine if it found in the xml file. However, i have a case where my query will result in a null. If i have no value, it would make an entry in the xml file and my query obviously results in null. My question is how do i cater for this without causing my programe to crash. obviously, singleordefault() doesnt work.
***************** EDITED *************************
I read what everyone said so far and it make sense but i am still having a problem.
if (query.Count() == 0)
{
Debug.WriteLine("NULL");
}
else {
var resultquery = query.SingleOrDefault();
Debug.WriteLine("NOT NULL");
}
OR
if (query == null)
{
Debug.WriteLine("NULL");
}
else {
var resultquery = query.SingleOrDefault();
Debug.WriteLine("NOT NULL");
}
OR
var resultquery = query.SingleOrDefault();
if (resultquery == null)
{
Debug.WriteLine("NULL Result");
}
else
{
Debug.WriteLine("NOT NULL");
}
I am getting a System.NullReferenceException error when the first part of the if statement is true. One user said to do this: var resultquery = query.SingleOrDefault(); then use my if..else statement to do the comparison. However i am getting the error at the point of assign query.singleofdefault() to resultquery. So i am lost.. hope someone can help. thank you
what i am trying to understand is this. the documentation states if the result query is 0 it will give a default value, if it is not, it will be a single value. so why doesnt this give a default value? [taken from the comments]
null is the default value for reference types. Apparently AllowancePaid is a reference type (a custom class).
What is the value you want when the there is no value found.
You could either do:
if (resultquery == null) {
// Logic for No result
} else {
// Logic for result found
}
Or you could force a default value
eg.
var resultquery = query.SingleOrDefault() ?? new AllowancePaid();
UPDATE
From the comments posted it appears that the null reference exception is actually caused within the query itself rather than by the assignment to resultquery and use of later.
This updated query should solve the issue
var query = from nm in xelement.Descendants("EmployeeFinance")
where nm.Element("EmpPersonal_Id") != null
&& (int)nm.Element("EmpPersonal_Id") == empID
&& nm.Element("Allow-GradeTax") != null
&& nm.Element("Allow-GradeTax").Elements("Amount") != null
select new AllowancePaid
{
gradeTaxId = nm.Element("Allow-GradeTax").Elements("Amount").Attributes("BenListId").Select(a => (int)a).ToList(),
gradeTaxAmt = nm.Element("Allow-GradeTax").Elements("Amount").Select(a => (double)a).ToList()
};
var resultquery = query.SingleOrDefault();
if (resultquery == null) {
Debug.WriteLine("NULL Result");
} else {
// Logic here
}
const string keyword = "manoj";
rsp.DataSource = company.GetCompanySearch(keyword);
rsp.DataBind();
public List<Company> GetCompanySearch(string keyword)
{
using (var context = huntableEntities.GetEntitiesWithNoLock())
{
List<Company> query = context.Companies.ToList();
if (!string.IsNullOrEmpty(keyword))
{
keyword = keyword.ToLower();
query = (List<Company>) query.Where(u=>u.CompanyName.Contains(keyword)
|| u.EmailAdress.Contains(keyword)
||u.MasterCountry.Description.Contains(keyword)
||u.MasterIndustry.Description.Contains(keyword)
||u.CompanyDescription.Contains(keyword)
||u.CompanyHeading.Contains(keyword));
}
return query.ToList();
}
}
The following code throwing the following exception:
{"Unable to cast object of type 'WhereListIterator1[Data.Company]' to type 'System.Collections.Generic.List1[Data.Company]'."}
"(List) query.Where()" is equal to "(List) (query.Where())", so this will throw that exception.
Should use query.Where().ToList() but not a explicit cast.
Further, better not put "List query = context.Companies.ToList();" before your "if" statement. In this case, no matter keyword is empty or not, it will query all records into memory and it will cause performance problem.
Can change to below
IQueryable<Company> query = context.Companies; //Remove ToList()
if (!string.IsNullOrEmpty(keyword))
{
keyword = keyword.ToLower();
// Remove cast
query = query.Where(u=>u.CompanyName.Contains(keyword)
|| u.EmailAdress.Contains(keyword)
||u.MasterCountry.Description.Contains(keyword)
||u.MasterIndustry.Description.Contains(keyword)
||u.CompanyDescription.Contains(keyword)
||u.CompanyHeading.Contains(keyword));
}
return query.ToList();
I know i can do this
var nv = HttpUtility.ParseQueryString(req.RawUrl);
But is there a way to convert this back to a url?
var newUrl = HttpUtility.Something("/page", nv);
Simply calling ToString() on the NameValueCollection will return the name value pairs in a name1=value1&name2=value2 querystring ready format. Note that NameValueCollection types don't actually support this and it's misleading to suggest this, but the behavior works here due to the internal type that's actually returned, as explained below.
Thanks to #mjwills for pointing out that the HttpUtility.ParseQueryString method actually returns an internal HttpValueCollection object rather than a regular NameValueCollection (despite the documentation specifying NameValueCollection). The HttpValueCollection automatically encodes the querystring when using ToString(), so there's no need to write a routine that loops through the collection and uses the UrlEncode method. The desired result is already returned.
With the result in hand, you can then append it to the URL and redirect:
var nameValues = HttpUtility.ParseQueryString(Request.QueryString.ToString());
string url = Request.Url.AbsolutePath + "?" + nameValues.ToString();
Response.Redirect(url);
Currently the only way to use a HttpValueCollection is by using the ParseQueryString method shown above (other than reflection, of course). It looks like this won't change since the Connect issue requesting this class be made public has been closed with a status of "won't fix."
As an aside, you can call the Add, Set, and Remove methods on nameValues to modify any of the querystring items before appending it. If you're interested in that see my response to another question.
string q = String.Join("&",
nvc.AllKeys.Select(a => a + "=" + HttpUtility.UrlEncode(nvc[a])));
Make an extension method that uses a couple of loops. I prefer this solution because it's readable (no linq), doesn't require System.Web.HttpUtility, and it supports duplicate keys.
public static string ToQueryString(this NameValueCollection nvc)
{
if (nvc == null) return string.Empty;
StringBuilder sb = new StringBuilder();
foreach (string key in nvc.Keys)
{
if (string.IsNullOrWhiteSpace(key)) continue;
string[] values = nvc.GetValues(key);
if (values == null) continue;
foreach (string value in values)
{
sb.Append(sb.Length == 0 ? "?" : "&");
sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value));
}
}
return sb.ToString();
}
Example
var queryParams = new NameValueCollection()
{
{ "order_id", "0000" },
{ "item_id", "1111" },
{ "item_id", "2222" },
{ null, "skip entry with null key" },
{ "needs escaping", "special chars ? = &" },
{ "skip entry with null value", null }
};
Console.WriteLine(queryParams.ToQueryString());
Output
?order_id=0000&item_id=1111&item_id=2222&needs%20escaping=special%20chars%20%3F%20%3D%20%26
This should work without too much code:
NameValueCollection nameValues = HttpUtility.ParseQueryString(String.Empty);
nameValues.Add(Request.QueryString);
// modify nameValues if desired
var newUrl = "/page?" + nameValues;
The idea is to use HttpUtility.ParseQueryString to generate an empty collection of type HttpValueCollection. This class is a subclass of NameValueCollection that is marked as internal so that your code cannot easily create an instance of it.
The nice thing about HttpValueCollection is that the ToString method takes care of the encoding for you. By leveraging the NameValueCollection.Add(NameValueCollection) method, you can add the existing query string parameters to your newly created object without having to first convert the Request.QueryString collection into a url-encoded string, then parsing it back into a collection.
This technique can be exposed as an extension method as well:
public static string ToQueryString(this NameValueCollection nameValueCollection)
{
NameValueCollection httpValueCollection = HttpUtility.ParseQueryString(String.Empty);
httpValueCollection.Add(nameValueCollection);
return httpValueCollection.ToString();
}
Actually, you should encode the key too, not just value.
string q = String.Join("&",
nvc.AllKeys.Select(a => $"{HttpUtility.UrlEncode(a)}={HttpUtility.UrlEncode(nvc[a])}"));
Because a NameValueCollection can have multiple values for the same key, if you are concerned with the format of the querystring (since it will be returned as comma-separated values rather than "array notation") you may consider the following.
Example
var nvc = new NameValueCollection();
nvc.Add("key1", "val1");
nvc.Add("key2", "val2");
nvc.Add("empty", null);
nvc.Add("key2", "val2b");
Turn into: key1=val1&key2[]=val2&empty&key2[]=val2b rather than key1=val1&key2=val2,val2b&empty.
Code
string qs = string.Join("&",
// "loop" the keys
nvc.AllKeys.SelectMany(k => {
// "loop" the values
var values = nvc.GetValues(k);
if(values == null) return new[]{ k };
return nvc.GetValues(k).Select( (v,i) =>
// 'gracefully' handle formatting
// when there's 1 or more values
string.Format(
values.Length > 1
// pick your array format: k[i]=v or k[]=v, etc
? "{0}[]={1}"
: "{0}={1}"
, k, HttpUtility.UrlEncode(v), i)
);
})
);
or if you don't like Linq so much...
string qs = nvc.ToQueryString(); // using...
public static class UrlExtensions {
public static string ToQueryString(this NameValueCollection nvc) {
return string.Join("&", nvc.GetUrlList());
}
public static IEnumerable<string> GetUrlList(this NameValueCollection nvc) {
foreach(var k in nvc.AllKeys) {
var values = nvc.GetValues(k);
if(values == null) { yield return k; continue; }
for(int i = 0; i < values.Length; i++) {
yield return
// 'gracefully' handle formatting
// when there's 1 or more values
string.Format(
values.Length > 1
// pick your array format: k[i]=v or k[]=v, etc
? "{0}[]={1}"
: "{0}={1}"
, k, HttpUtility.UrlEncode(values[i]), i);
}
}
}
}
As has been pointed out in comments already, with the exception of this answer most of the other answers address the scenario (Request.QueryString is an HttpValueCollection, "not" a NameValueCollection) rather than the literal question.
Update: addressed null value issue from comment.
The short answer is to use .ToString() on the NameValueCollection and combine it with the original url.
However, I'd like to point out a few things:
You cant use HttpUtility.ParseQueryString on Request.RawUrl. The ParseQueryString() method is looking for a value like this: ?var=value&var2=value2.
If you want to get a NameValueCollection of the QueryString parameters just use Request.QueryString().
var nv = Request.QueryString;
To rebuild the URL just use nv.ToString().
string url = String.Format("{0}?{1}", Request.Path, nv.ToString());
If you are trying to parse a url string instead of using the Request object use Uri and the HttpUtility.ParseQueryString method.
Uri uri = new Uri("<THE URL>");
var nv = HttpUtility.ParseQueryString(uri.Query);
string url = String.Format("{0}?{1}", uri.AbsolutePath, nv.ToString());
I always use UriBuilder to convert an url with a querystring back to a valid and properly encoded url.
var url = "http://my-link.com?foo=bar";
var uriBuilder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query.Add("yep", "foo&bar");
uriBuilder.Query = query.ToString();
var result = uriBuilder.ToString();
// http://my-link.com:80/?foo=bar&yep=foo%26bar
In AspNet Core 2.0 you can use QueryHelpers AddQueryString method.
As #Atchitutchuk suggested, you can use QueryHelpers.AddQueryString in ASP.NET Core:
public string FormatParameters(NameValueCollection parameters)
{
var queryString = "";
foreach (var key in parameters.AllKeys)
{
foreach (var value in parameters.GetValues(key))
{
queryString = QueryHelpers.AddQueryString(queryString, key, value);
}
};
return queryString.TrimStart('?');
}
This did the trick for me:
public ActionResult SetLanguage(string language = "fr_FR")
{
Request.UrlReferrer.TryReadQueryAs(out RouteValueDictionary parameters);
parameters["language"] = language;
return RedirectToAction("Index", parameters);
}
You can use.
var ur = new Uri("/page",UriKind.Relative);
if this nv is of type string you can append to the uri first parameter.
Like
var ur2 = new Uri("/page?"+nv.ToString(),UriKind.Relative);