【问题标题】:Possible to search case-insensitive with JSONPath?可以使用 JSONPath 搜索不区分大小写吗?
【发布时间】:2016-06-12 18:39:57
【问题描述】:

使用 JSON.NET 的 SelectToken method 选择带有 JSONPath 的令牌,我发现无法指定搜索不区分大小写。

例如

json.SelectToken("$.maxAppVersion")

应该返回一个匹配的令牌,无论它是写成maxappversionMAXAPPVERSION还是任何其他大小写。

我的问题:

是否有官方方式或至少有解决方法以不区分大小写的方式使用 JSONPath?

(我找到的最接近的是 this similar question 用于 JSON 的 Java 实现)

【问题讨论】:

    标签: .net json json.net jsonpath


    【解决方案1】:

    这在 Json.NET 8.0.2 版本中没有实现。

    JSONPath 属性名称匹配通过两个类完成:FieldFilter 用于简单名称匹配,ScanFilter 用于递归搜索。 FieldFilter 具有以下代码,其中oJObject

    JToken v = o[Name];
    if (v != null)
    {
        yield return v;
    }
    

    在内部JObject 使用JPropertyKeyedCollection 来保存其属性,而后者又使用以下比较器进行属性名称查找:

    private static readonly IEqualityComparer<string> Comparer = StringComparer.Ordinal;
    

    因此区分大小写。同样,ScanFilter 有:

    JProperty e = value as JProperty;
    if (e != null)
    {
        if (e.Name == Name)
        {
            yield return e.Value;
        }
    }
    

    这也是区分大小写的。

    JSONPath standard 中没有提到不区分大小写的匹配,所以我认为您想要的东西根本无法开箱即用。

    作为一种解决方法,您可以为此添加自己的扩展方法:

    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));
        }
    }
    

    然后将它们与标准的SelectTokens 调用链接在一起,例如:

    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
    

    (相关地,请参阅 Json.NET 问题 Provide a way to do case-sensitive property deserialization,它请求区分大小写的合同解析器以与 LINQ-to-JSON 的区分大小写保持一致。)

    【讨论】:

    • 看起来你的扩展方法只深入了一层,这并不能解决SelectToken 的问题。你的扩展方法比GetValue有优势吗?
    • @KyleDelaney - 我已经很久没有回答这个问题了,但是GetValue(String, StringComparison) 返回一个 single 值,而扩展方法返回多个值,这在以下情况下可能很有用有多个属性仅因大小写而异。
    【解决方案2】:

    当我想获得令牌并且不必担心大小写时,我会这样做:

    var data = JObject.Parse(message.Data);
    var dataDictionary = new Dictionary<string, JToken>(data.ToObject<IDictionary<string, JToken>>(),
                                                            StringComparer.CurrentCultureIgnoreCase);
    

    如果有任何嵌套结构我需要担心,那么我必须为这些结构再做一次,但 StringComparer 意味着 dataDictionary["CampaignId"].ToString();dataDictionary["campaignId"].ToString(); 都可以工作,我得到了两者。

    【讨论】:

    • 如果只有一层,那么你可以使用JObject.GetValue
    【解决方案3】:

    Newtonsoft 在不支持这一点的情况下侥幸逃脱真是令人惊讶。我必须编写一个自定义 JToken 扩展来支持这一点。我不需要整个 JSONPath,只需要一些基本的路径查询。下面是我使用的代码

    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;
    }
    

    使用上面的代码我可以解析 JSON 如下

    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
    

    代码到整个扩展available here

    【讨论】:

      【解决方案4】:

      对我有用的是将所有内容都转换为大写,然后在大写字符串中搜索,但值是从原始字符串返回的:

      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;
      }
      

      【讨论】:

      • 不错的一个!我猜可能最好使用ToUpperInvariant
      • 这有问题,如果有多个具有相同属性值的对象,IndexOf 将始终抓取第一个。此外,如果对象包含 null、true、false,它会在 JObject.Parse 方法中失败。这些必须转换为小写字母。这应该适用于没有具有相似属性/值的数组的简单对象。
      【解决方案5】:

      一个快速肮脏的黑客解决方案是将对象键转换为大写,然后执行 JsonPath 查询,这是您案例的一个简单示例(只是为了给您一个想法)。要包含深入搜索,您必须转换子对象中的所有键(需要递归):

      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
      }
      

      请注意,此代码未经测试。

      更新: 为了更快的键转换,您可以执行正则表达式替换来替换 json 字符串中的所有键。模式类似于:"\"([^\"]+)\"\s*:"

      【讨论】:

        猜你喜欢
        • 2013-07-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-21
        • 2010-09-15
        • 2013-09-26
        • 2023-03-20
        • 1970-01-01
        相关资源
        最近更新 更多