Reputation: 40736
Using the SelectToken
method of JSON.NET to select a token with JSONPath, I found no way to specifiy that the search should be case-insensitive.
E.g.
json.SelectToken("$.maxAppVersion")
should return a matching token, no matter whether it is written maxappversion
, MAXAPPVERSION
or any other casing.
My question:
Is there an official way or at least a work-around to use JSONPath in a case-insensitive way?
(The closest I found was this similar question for a Java implementation of JSON)
Upvotes: 15
Views: 17495
Reputation: 700
I took inspiration from this answer and made all keys lowercase first. If you also makes your JSONPath lowercase it will be case-insensitive. This works well if you are creating the JObject using JObject.FromObject.
First create a NamingStrategy
public class LowercaseNamingStrategy : NamingStrategy
{
protected override string ResolvePropertyName(string name)
{
return name.ToLowerInvariant();
}
}
Then use it to create your JObject
var jObject = JObject.FromObject(
anObject,
new JsonSerializer
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new LowercaseNamingStrategy()
}
}
);
var value = jObject.SelectToken(path.ToLowerInvariant())
Upvotes: 1
Reputation: 22759
A quick dirty hackish solution is to convert the object keys to upper-case then perform the JsonPath query, a simple example for your case (just to give you an idea). To include in-depth search then you must convert all of the keys in the child objects (requires recursion):
var path = "maxappversion";
var json = "{\"maxAppVersion\": 1}";
var type = typeof(Dictionary<string, int>);
// This will convert the json str to actual Dictionary object
var jsonObj = JsonSerializer.Deserialize(json, type);
// Loop through all of the properties in the dictionary and convert the keys to uppercase
foreach (var kpv in jsonObj) jsonObj[kpv.Key.ToUpperCase()] = kpv.Value;
// Look for the path
try
{
// This will return list of tokens, if not found, exception will be thrown
var tokens = JObject.FromObject(jsonObj).SelectTokens(path.ToUpperCase(), true);
var value = (int)tokens.FirstOrDefault(); // Use linq or just loop
}
catch (JsonException)
{
// PathNotFound
}
Please note that this code isn't tested.
Update:
For faster key conversion, you can do a regex replace to replace all of the keys in the json string. The pattern would be something like: "\"([^\"]+)\"\s*:"
Upvotes: 1
Reputation: 11
What worked for me is converting everything to uppercase then search in the uppercase string, but the value is returned from the original string:
public static string SelectValue(this string jsonString, string jsonPath)
{
string result = null;
string jsonStringToUpper = jsonString.ToUpper();
string jsonPathToUpper = jsonPath.ToUpper();
var jsonObj = JObject.Parse(jsonStringToUpper);
string valueToUpper = (string)jsonObj.SelectToken(jsonPathToUpper);
if (!string.IsNullOrEmpty(valueToUpper))
{
int upperCaseIndex = jsonStringToUpper.IndexOf(valueToUpper);
result = jsonString.Substring(upperCaseIndex, valueToUpper.Length);
}
return result;
}
Upvotes: 1
Reputation: 6776
It's really surprising that Newtonsoft gets away without supporting this. I had to write a custom JToken extension to support this. I did not need the entire JSONPath, needed just a few basic path queries. Below is the code I used
public static JToken GetPropertyFromPath(this JToken token, string path)
{
if (token == null)
{
return null;
}
string[] pathParts = path.Split(".");
JToken current = token;
foreach (string part in pathParts)
{
current = current.GetProperty(part);
if (current == null)
{
return null;
}
}
return current;
}
public static JToken GetProperty(this JToken token, string name)
{
if (token == null)
{
return null;
}
var obj = token as JObject;
JToken match;
if (obj.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out match))
{
return match;
}
return null;
}
With the above code I can parse JSON as follows
var obj = JObject.Parse(someJson);
JToken tok1 = obj.GetPropertyFromPath("l1.l2.l3.name"); // No $, or other json path cliché
JToken tok2 = obj.GetProperty("name");
string name = obj.StringValue("name"); // Code in the link below
Code to entire extension available here
Upvotes: 5
Reputation: 866
When I want to get a token and not have to worry about case I do this:
var data = JObject.Parse(message.Data);
var dataDictionary = new Dictionary<string, JToken>(data.ToObject<IDictionary<string, JToken>>(),
StringComparer.CurrentCultureIgnoreCase);
If there are any nested structures I need to worry about then I have to do it again for those but that StringComparer means either dataDictionary["CampaignId"].ToString();
or dataDictionary["campaignId"].ToString();
will work and I get both.
Upvotes: 2
Reputation: 116980
This is not implemented in Json.NET as of version 8.0.2.
JSONPath property name matching is done with two classes: FieldFilter
for simple name matches, and ScanFilter
for recursive searches. FieldFilter
has the following code, where o
is a JObject
:
JToken v = o[Name];
if (v != null)
{
yield return v;
}
Internally JObject
uses a JPropertyKeyedCollection
to hold its properties, which in turn uses the following comparer for property name lookups:
private static readonly IEqualityComparer<string> Comparer = StringComparer.Ordinal;
It is thus case-sensitive. Similarly, ScanFilter
has:
JProperty e = value as JProperty;
if (e != null)
{
if (e.Name == Name)
{
yield return e.Value;
}
}
Which is also case sensitive.
There's no mention of case-insensitive matching in the JSONPath standard so I think what you want simply isn't available out of the box.
As a workaround, you could add your own extension methods for this:
public static class JsonExtensions
{
public static IEnumerable<JToken> CaseSelectPropertyValues(this JToken token, string name)
{
var obj = token as JObject;
if (obj == null)
yield break;
foreach (var property in obj.Properties())
{
if (name == null)
yield return property.Value;
else if (string.Equals(property.Name, name, StringComparison.OrdinalIgnoreCase))
yield return property.Value;
}
}
public static IEnumerable<JToken> CaseSelectPropertyValues(this IEnumerable<JToken> tokens, string name)
{
if (tokens == null)
throw new ArgumentNullException();
return tokens.SelectMany(t => t.CaseSelectPropertyValues(name));
}
}
And then chain them together with standard SelectTokens
calls, for instance:
var root = new { Array = new object[] { new { maxAppVersion = "1" }, new { MaxAppVersion = "2" } } };
var json = JToken.FromObject(root);
var tokens = json.SelectTokens("Array[*]").CaseSelectPropertyValues("maxappversion").ToList();
if (tokens.Count != 2)
throw new InvalidOperationException(); // No exception thrown
(Relatedly, see the Json.NET issue Provide a way to do case-sensitive property deserialization which requests a case-sensitive contract resolver for consistency with the case-sensitivity of LINQ-to-JSON.)
Upvotes: 12