确定一个 JSON 对象是否包含在另一个 JSON 对象中

huangapple go评论58阅读模式
英文:

Determine whether JSON object is included in another JSON object

问题

不久前我不得不创建一个方法,该方法将确定第一个 JSON 对象是否包含在第二个 JSON 对象中。然后我创建了一个非常简单的方法:

public static partial class JsonOperations
{
    [GeneratedRegex(@"\[\d+\]", RegexOptions.Compiled)]
    private static partial Regex FindArrayIndexers();

    private static IEnumerable<string> GetJsonValuesIdentifiers(IEnumerable<JValue> values)
    {
        return values.Select(p => $"{FindArrayIndexers().Replace(p.Path, string.Empty)}:{p.Value?.ToString()}");
    }

    public static bool IsIncludedIn(this JObject first, JObject second)
    {
        var firstIdentifiers = GetJsonValuesIdentifiers(first.Descendants().OfType<JValue>());
        var secondIdentifiers = GetJsonValuesIdentifiers(second.Descendants().OfType<JValue>());

        return firstIdentifiers.All(secondIdentifiers.Contains);
    }
}

它是如何工作的?我获取 JSON 对象的后代,它们是 JValue,所以是简单的对象,如 int、string 等。稍后,我获取每个 JValueJSON Path,并使用正则表达式将其中的数组索引移除。例如:.type.arr[0].xyz 被转换为 .type.arr.xyz,因此这些值的顺序无关紧要。然后我将其与值结合起来,得到标识符,例如 .type.arr.xyz:some-string-value。最后,我只是检查第一个 JSON 对象的所有标识符是否都包含在第二个 JSON 对象中。

似乎运行良好,但我注意到了一个 bug,它在我的情况下造成了严重的安全问题。

想象一个对象:

{
   "geolocation":[
      {
         "lat":-32.364,
         "lng":158.207
      },
      {
         "lat":-35.364,
         "lng":153.207
      }
   ]
}

和第二个:

{
   "geolocation":[
      {
         "lat":-32.364,
         "lng":153.207
      },
      {
         "lat":-35.364,
         "lng":158.207
      }
   ]
}

我提供的方法会说第一个对象包含在第二个中,但这并不正确。是的,我已经提到的“标识符”都包含在内,但“整体看待”,在这种情况下,我们看到数组的两个对象指向不同的坐标。

另外,一般假设的是,比较的 JSON 总是具有相同的结构。如果不是这样,那么第一个就不能包含在第二个中。

我希望你理解了问题,我很难向你解释,但如果你有任何问题,请提出。

英文:

Some time ago I had to create method that will determine whether first JSON object is included in second JSON object. Then I created very simple method:

public static partial class JsonOperations
{
    [GeneratedRegex(@&quot;\[\d+\]&quot;, RegexOptions.Compiled)]
    private static partial Regex FindArrayIndexers();

    private static IEnumerable&lt;string&gt; GetJsonValuesIdentifiers(IEnumerable&lt;JValue&gt; values)
    {
        return values.Select(p =&gt; $&quot;{FindArrayIndexers().Replace(p.Path, string.Empty)}:{p.Value?.ToString()}&quot;);
    }

    public static bool IsIncludedIn(this JObject first, JObject second)
    {
        var firstIdentifiers = GetJsonValuesIdentifiers(first.Descendants().OfType&lt;JValue&gt;());
        var secondIdentifiers = GetJsonValuesIdentifiers(second.Descendants().OfType&lt;JValue&gt;());

        return firstIdentifiers.All(secondIdentifiers.Contains);
    }
}

How it works? I get descendants of JSON object which are JValue, so simple objects like int, string etc. Later I get JSON Path of every JValue and remove array indexers from it using REGEX. For example: .type.arr[0].xyz is transformed to .type.arr.xyz, so the order of these values doesn't matter. Later I combine it with value and I get identifier, e.g. .type.arr.xyz:some-string-value. At the end I just check if all identifiers from first JSON object are contained in second JSON object.

It seemed to work fine, but I noticed bug that causes serious security issue in my case.

Imagine a object like:

{
   &quot;geolocation&quot;:[
      {
         &quot;lat&quot;:-32.364,
         &quot;lng&quot;:158.207
      },
      {
         &quot;lat&quot;:-35.364,
         &quot;lng&quot;:153.207
      }
   ]
}

And the second:

{
   &quot;geolocation&quot;:[
      {
         &quot;lat&quot;:-32.364,
         &quot;lng&quot;:153.207 &lt;-- It&#39;s exchanged
      },
      {
         &quot;lat&quot;:-35.364,
         &quot;lng&quot;:158.207 &lt;-- Exchanged with this
      }
   ]
}

The method I provided will say that first object is included in second, but that's not true. Yes, the "identifiers" I already mentioned are all included, but "looking at it as a whole", in this case, we see, that the two objects of array says about different coordinates.

Also, general assumption is that the compared JSONs have always the same structure. If not, then first cannot be included in second.

I hope you understood problem, it's hard to explain for me, but if you have any questions please ask.

答案1

得分: -1

以下是您要翻译的内容:

成功的关键是通过对JSON标识符进行分组来提供更多的上下文。以下是完全可用的代码:

public static partial class JsonOperations
{
    [GeneratedRegex(@"\[\d+\]", RegexOptions.Compiled)]
    private static partial Regex FindArrayIndexers();

    private static IEnumerable<IGrouping<string, string>> GetGroupedIdentifiers(IEnumerable<JValue> values)
    {
        // 按照路径中的最后一个']'字符进行分组,以便最后一个数组索引器。
        // 这样做可以避免将不同对象内具有相同属性和相同值的属性混合在一个数组中。
        return values.GroupBy(p => p.Path.IndexOf(']') != -1 ? p.Path[0..(p.Path.LastIndexOf(']') + 1)] : p.Path,
            v => $"{FindArrayIndexers().Replace(v.Path, string.Empty)}:{v.Value?.ToString()}");
    }

    public static bool IsIncludedIn(this JObject first, JObject second)
    {
        var firstGroupedIdentifiers = GetGroupedIdentifiers(first.Descendants().OfType<JValue>());
        var secondGroupedIdentifiers = GetGroupedIdentifiers(second.Descendants().OfType<JValue>());

        foreach (var firstGrouping in firstGroupedIdentifiers)
        {
            // sg - second grouping
            // fgId - identifier from first grouping
            // sgId - identifier from second grouping
            // Work: whether there is any grouping from secondGroupedIdentifiers where all of first grouping (currently iterated) identifiers are contained in second grouping
            var appropriateGroupingExists = secondGroupedIdentifiers.Any(sg =>
                firstGrouping.Select(fgId => fgId).All(fgId => sg.Select(sgId => sgId).Contains(fgId)));

            if (!appropriateGroupingExists)
                return false;
        }

        return true;
    }
}

我已经提供了代码的中文翻译,没有包括额外的内容。

英文:

The key to success is to provide more context to JSON identifiers by grouping them. That's fully working code:

public static partial class JsonOperations
{
    [GeneratedRegex(@&quot;\[\d+\]&quot;, RegexOptions.Compiled)]
    private static partial Regex FindArrayIndexers();

    private static IEnumerable&lt;IGrouping&lt;string, string&gt;&gt; GetGroupedIdentifiers(IEnumerable&lt;JValue&gt; values)
    {
        // Group by path to last ] characters, so to the last array indexer.
        // Doing this we avoid mixing properties from different objects inside one array but with the same properties with the same values
        return values.GroupBy(p =&gt; p.Path.IndexOf(&#39;]&#39;) != -1 ? p.Path[0..(p.Path.LastIndexOf(&#39;]&#39;) + 1)] : p.Path,
            v =&gt; $&quot;{FindArrayIndexers().Replace(v.Path, string.Empty)}:{v.Value?.ToString()}&quot;);
    }

    public static bool IsIncludedIn(this JObject first, JObject second)
    {
        var firstGroupedIdentifiers = GetGroupedIdentifiers(first.Descendants().OfType&lt;JValue&gt;());
        var secondGroupedIdentifiers = GetGroupedIdentifiers(second.Descendants().OfType&lt;JValue&gt;());

        foreach (var firstGrouping in firstGroupedIdentifiers)
        {
            // sg - second grouping
            // fgId - identifier from first grouping
            // sgId - identifier from second grouping
            // Work: whether there is any grouping from secondGroupedIdentifiers where all of first grouping (currently iterated) identifiers are contained in second grouping
            var appropriateGroupingExists = secondGroupedIdentifiers.Any(sg =&gt;
                firstGrouping.Select(fgId =&gt; fgId).All(fgId =&gt; sg.Select(sgId =&gt; sgId).Contains(fgId)));

            if (!appropriateGroupingExists)
                return false;
        }

        return true;
    }
}

I have tested in on JSON provided in question and generated by: https://json-generator.com

Please notice assumption contained in question. This code works only (in accordance with assumption) if JSON structures are the same. If they are not, it won't work.

huangapple
  • 本文由 发表于 2023年5月7日 22:37:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76194577.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定