【问题标题】:Compare two arbitrary JToken-s of the same structure比较两个相同结构的任意 JToken-s
【发布时间】:2015-10-08 18:07:07
【问题描述】:

提前致谢。感谢您的帮助。

我想比较两个具有相同类型和结构的任意 JToken(来自 NewtonSoft 的 Json.Net)。

static int CompareTokens(JToken x, JToken y);  
// possible output: 0 / 1 / -1

主要目标是能够使用这种方法对两个Json字符串进行排序,这样即使一开始它们有相同的数据,但是顺序不同,最终这些都是两个完全相同的字符串。所以排序标准并不重要,重要的是这个标准总是相同的。并且应该考虑到数据的每一个小元素。

JToken 可以是以下几种类型之一:Array, Boolean, Date, Float, Guid, Integer, Null, Object, Property, String, TimeSpan, Uri。我不考虑比较Bytes, Comment, Constructor, None, Undefined, Raw

  • 如果能对比较 JArraysJObjects 有所了解,那就太好了。这应该是一些递归比较,因为 JArrays 可能由其他 JArraysJObjects 组成,反之亦然。任何想法都将不胜感激。
  • 但是了解比较简单类型也会很有帮助。我更想知道如何从 JToken 转换为实际类型(而不是知道如何在逻辑上做到这一点)。
  • JValue 实现了 IComparable,但我不知道如何将简单类型的 JToken 转换为 JValue。了解这一点也会有所帮助。

这是一个相当复杂的问题。如果我知道怎么做,我会给它+100。对不起我的英语。

【问题讨论】:

  • 使用JsonConvert.DeserializeObject<T> 并直接解析为预定义的类,因为您已经知道传入的数据。这样您就可以对所有熟悉的数据类型进行标准比较。
  • 1) 看看source code for JToken.DeepEquals,它可能会给你一些想法。 2)您想如何比较1"1"等可转换值?
  • @dbc 这就是为什么他应该解析成一个对象......在那里他可以指定类型并相应地处理它......
  • 2) JValue implements IComparable<JValue>,这是一个开始。
  • 谁会使用任意的 getHashCode...我永远不会使用 getHashCode,除非我自己编写了它,或者它明确说明它是有用的。

标签: c# json json.net


【解决方案1】:

在 Linq-to-JSON 中,JValue 表示原始值(字符串、数字、布尔值等)。它实现了IComparable<JValue>,因此 Json.NET 会为您处理原始值的排序。

在此基础上,您将需要并行递归地降低两个 JToken 对象层次结构。当您遇到具有不同 .Net 类型或不同属性(如果不是 JValue)或具有不同值(如果是 JValue)的第一个令牌时,您需要返回比较值。

请记住以下几点:

  • 比较方法应该是自反的、反对称的和传递的。
  • 不同 .Net 类型的容器令牌需要以某种一致的方式按类型排序。
  • JArrayJConstructor 的子令牌是有序的。
  • JObject 的子令牌不是,因此需要以某种稳定、对称的方式比较它们。按属性名称的顺序走似乎可行。
  • 没有明显的方法可以比较 JRaw,所以不要尝试,否则会引发异常。

以下是原型实现:

public class JTokenComparer : IComparer<JToken>
{
    public static JTokenComparer Instance { get { return instance; } }

    static JTokenComparer instance;

    static JTokenComparer()
    {
        instance = new JTokenComparer();
    }

    readonly Dictionary<Type, KeyValuePair<int, IComparer<JToken>>> dict;

    JTokenComparer()
    {
        dict = new Dictionary<Type, KeyValuePair<int, IComparer<JToken>>>
        {
            // Order chosen semi-arbitrarily.  Putting values first seems reasonable though.
            {typeof(JValue), new KeyValuePair<int, IComparer<JToken>>(0, new JValueComparer()) },
            {typeof(JProperty), new KeyValuePair<int, IComparer<JToken>>(1, new JPropertyComparer()) },
            {typeof(JArray), new KeyValuePair<int, IComparer<JToken>>(2, new JArrayComparer()) },
            {typeof(JObject), new KeyValuePair<int, IComparer<JToken>>(3, new JObjectComparer()) },
            {typeof(JConstructor), new KeyValuePair<int, IComparer<JToken>>(4, new JConstructorComparer()) },
        };
    }

    #region IComparer<JToken> Members

    public int Compare(JToken x, JToken y)
    {
        if (x is JRaw || y is JRaw)
            throw new InvalidOperationException("Tokens of type JRaw cannot be sorted");
        if (object.ReferenceEquals(x, y))
            return 0;
        else if (x == null)
            return -1;
        else if (y == null)
            return 1;

        var typeData1 = dict[x.GetType()];
        var typeData2 = dict[y.GetType()];

        int comp;
        if ((comp = typeData1.Key.CompareTo(typeData2.Key)) != 0)
            return comp;
        if (typeData1.Value != typeData2.Value)
            throw new InvalidOperationException("inconsistent dictionary values"); // Internal error
        return typeData2.Value.Compare(x, y);
    }

    #endregion
}

abstract class JTokenComparerBase<TJToken> : IComparer<JToken> where TJToken : JToken
{
    protected TJToken CheckType(JToken item)
    {
        if (item != null && item.GetType() != typeof(TJToken))
            throw new ArgumentException(string.Format("Actual type {0} of token \"{1}\" does not match expected type {2}", item.GetType(), item, typeof(TJToken)));
        return (TJToken)item;
    }

    protected bool TryBaseCompare(TJToken x, TJToken y, out int comparison)
    {
        CheckType(x);
        CheckType(y);
        if (object.ReferenceEquals(x, y))
        {
            comparison = 0;
            return true;
        }
        else if (x == null)
        {
            comparison = -1;
            return true;
        }
        else if (y == null)
        {
            comparison = 1;
            return true;
        }
        comparison = 0;
        return false;
    }

    protected abstract int CompareDerived(TJToken x, TJToken y);

    protected int TokenCompare(JToken x, JToken y)
    {
        var tx = CheckType(x);
        var ty = CheckType(y);
        int comp;
        if (TryBaseCompare(tx, ty, out comp))
            return comp;
        return CompareDerived(tx, ty);
    }

    #region IComparer<JToken> Members

    int IComparer<JToken>.Compare(JToken x, JToken y)
    {
        return TokenCompare(x, y);
    }

    #endregion
}

abstract class JContainerOrderedComparerBase<TJToken> : JTokenComparerBase<TJToken> where TJToken : JContainer
{
    protected int CompareItemsInOrder(TJToken x, TJToken y)
    {
        int comp;
        // Dictionary order: sort on items before number of items.
        for (int i = 0, n = Math.Min(x.Count, y.Count); i < n; i++)
            if ((comp = JTokenComparer.Instance.Compare(x[i], y[i])) != 0)
                return comp;
        if ((comp = x.Count.CompareTo(y.Count)) != 0)
            return comp;
        return 0;
    }
}

class JPropertyComparer : JTokenComparerBase<JProperty>
{
    protected override int CompareDerived(JProperty x, JProperty y)
    {
        int comp;
        if ((comp = x.Name.CompareTo(y.Name)) != 0)
            return comp;
        return JTokenComparer.Instance.Compare(x.Value, y.Value);
    }
}

class JObjectComparer : JTokenComparerBase<JObject>
{
    protected override int CompareDerived(JObject x, JObject y)
    {
        int comp;
        // Dictionary order: sort on items before number of items.
        // Order both property sequences to preserve reflexivity.
        foreach (var propertyComp in x.Properties().OrderBy(p => p.Name).Zip(y.Properties().OrderBy(p => p.Name), (xp, yp) => JTokenComparer.Instance.Compare(xp, yp)))
            if (propertyComp != 0)
                return propertyComp;
        if ((comp = x.Count.CompareTo(y.Count)) != 0)
            return comp;
        return 0;
    }
}

class JArrayComparer : JContainerOrderedComparerBase<JArray>
{
    protected override int CompareDerived(JArray x, JArray y)
    {
        int comp;
        if ((comp = CompareItemsInOrder(x, y)) != 0)
            return comp;
        return 0;
    }
}

class JConstructorComparer : JContainerOrderedComparerBase<JConstructor>
{
    protected override int CompareDerived(JConstructor x, JConstructor y)
    {
        int comp;
        if ((comp = x.Name.CompareTo(y.Name)) != 0)
            return comp;
        if ((comp = CompareItemsInOrder(x, y)) != 0)
            return comp;
        return 0;
    }
}

class JValueComparer : JTokenComparerBase<JValue>
{
    protected override int CompareDerived(JValue x, JValue y)
    {
        return Comparer<JToken>.Default.Compare(x, y); // JValue implements IComparable<JValue>
    }
}

经过轻微测试prototype fiddle

【讨论】:

  • 哇!这是一些非常棒的工作!多谢!到目前为止,我一直在做的事情并没有那么精心设计。我希望我不要忘记在两天内打出 100 分,希望你能得到它们。但是分数并不重要,我很幸运,因为你救了我这一天!
  • 我是新来的,碰巧在我接受了答案后,放 +100 的可能性就消失了......
  • 目前,在比较具有相同数据但顺序不同的两个字符串数组时,比较无法正常工作。可能对某人了解有用。
  • @AndreyKomissarov 在回答被接受后,仍然可以为问题添加赏金——您只需要在最初提出问题后等待 48 小时。之后,问题的 cmets 部分下方应该有一个“开始赏金”链接。请参阅帮助中心的What is a bounty? How can I start one?
  • @dbc 对于“不同顺序的字符串数组”,我真的很抱歉。这是我的错误,我的代码,我没有注意到。你的代码似乎有效。
【解决方案2】:

实际上,这可以用更少的代码来完成。不太好,因为使用 字符串比较 而不是 JValue 比较。

以下不是我自己问题的确切答案,但目标已经实现。

    public static JToken Normalize(this JToken token)
    {
        var result = token;

        switch (token.Type)
        {
            case JTokenType.Object:
                var jObject = (JObject)token;

                if (jObject != null && jObject.HasValues)
                {
                    var newObject = new JObject();

                    foreach (var property in jObject.Properties().OrderBy(x => x.Name).ToList())
                    {
                        var value = property.Value as JToken;
                        if (value != null)
                        {
                            value = Normalize(value);
                        }

                        newObject.Add(property.Name, value);
                    }
                    return newObject;
                }

                break;

            case JTokenType.Array:

                var jArray = (JArray)token;

                if (jArray != null && jArray.Count > 0)
                {
                    var normalizedArrayItems = jArray
                        .Select(x => Normalize(x))
                        .OrderBy(x => x.ToString(), StringComparer.Ordinal);

                    result = new JArray(normalizedArrayItems);
                }

                break;
            default:
                break;
        }

        return result;
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-18
    相关资源
    最近更新 更多