比较两个相同结构的任意JToken-s

提前致谢。 我感谢任何帮助。

我想比较两个相同类型和结构的任意JTokens(来自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可能包含其他JArraysJObject ,反之亦然。 任何想法将不胜感激。
  • 但是,了解比较更简单的类型也会非常有帮助。 我想知道如何从JToken转换为实际类型(而不是知道如何在逻辑上进行)。
  • JValue已实现IComparable ,但我没有弄清楚如何将简单类型的JToken转换为JValue 。 了解这一点也会有所帮助。

这是一个很常见的问题。 如果我弄清楚如何做到这一点,我会在上面放一个+100。 抱歉我的英语。

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

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

请记住以下内容:

  • 比较方法应该是反身的,反对称的和传递的。
  • 不同.Net类型的容器令牌需要以某种一致的方式按类型排序。
  • 订购了JArrayJConstructor的子令牌。
  • JObject的子标记不是,因此需要以一种稳定,对称的方式进行比较。 按照属性名称顺序行走似乎有效。
  • 没有明显的方法来比较JRaw ,所以不要尝试,并抛出exception。

以下是原型实现:

 public class JTokenComparer : IComparer { public static JTokenComparer Instance { get { return instance; } } static JTokenComparer instance; static JTokenComparer() { instance = new JTokenComparer(); } readonly Dictionary>> dict; JTokenComparer() { dict = new Dictionary>> { // Order chosen semi-arbitrarily. Putting values first seems reasonable though. {typeof(JValue), new KeyValuePair>(0, new JValueComparer()) }, {typeof(JProperty), new KeyValuePair>(1, new JPropertyComparer()) }, {typeof(JArray), new KeyValuePair>(2, new JArrayComparer()) }, {typeof(JObject), new KeyValuePair>(3, new JObjectComparer()) }, {typeof(JConstructor), new KeyValuePair>(4, new JConstructorComparer()) }, }; } #region IComparer 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 : IComparer 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 Members int IComparer.Compare(JToken x, JToken y) { return TokenCompare(x, y); } #endregion } abstract class JContainerOrderedComparerBase : JTokenComparerBase 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 { 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 { 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 { protected override int CompareDerived(JArray x, JArray y) { int comp; if ((comp = CompareItemsInOrder(x, y)) != 0) return comp; return 0; } } class JConstructorComparer : JContainerOrderedComparerBase { 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 { protected override int CompareDerived(JValue x, JValue y) { return Comparer.Default.Compare(x, y); // JValue implements IComparable } } 

轻微测试的原型小提琴 。

实际上,这可以用更少的代码完成。 不太好,因为使用字符串比较而不是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; }