2015-10-08 2 views
3

Заранее спасибо. Я ценю любую помощь.Сравните два произвольных JToken-s той же структуры

Я хотел бы сравнить два произвольных JTokens того же типа и структуры (Json.Net от NewtonSoft).

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.

  • Было бы здорово, чтобы получить некоторое представление о сравнении JArrays и JObjects. Это будет некоторое рекурсивное сравнение, потому что JArrays может состоять из других JArrays и JObjects и наоборот. Любая идея была бы оценена.
  • Но знать о сравнении более простых типов также было бы очень полезно. Мне интересно узнать, как конвертировать из JToken в фактический тип (а не знать, как это сделать логически).
  • JValue имеет IComparable реализован, но я не выяснить, как преобразовать простой напечатанное JToken к JValue. Знание об этом также было бы полезно.

Это довольно большой вопрос. Если я выясню, как это сделать, я поставлю на него +100. И извините за мой английский.

+0

Использование '' JsonConvert.DeserializeObject и анализировать непосредственно в заранее определенном классе, потому что вы уже знаете, поступающие данные. Таким образом, вы можете использовать стандартное сравнение со всеми знакомыми типами данных. – SkryptX

+0

1) Взгляните на [исходный код] (https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Linq/JToken.cs) для ['JToken.DeepEquals' ] (http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Linq_JToken_DeepEquals.htm), он может дать вам некоторые идеи. 2) Как вы хотите сравнить конвертируемые значения, такие как '1' и' "1" '? – dbc

+0

@dbc Вот почему он должен разбираться в Object ... Там он может указать тип и обработать его соответствующим образом ... – SkryptX

ответ

7

В Linq-to-JSON JValue представляет собой примитивное значение (строка, число, логическое значение и т. Д.). Он реализует IComparable<JValue>, поэтому Json.NET заботится о сортировке примитивных значений для вас.

Исходя из этого, вам потребуется рекурсивно спустить две иерархии объектов JToken параллельно. Когда вы сталкиваетесь с первым токеном с другим типом .Net или разными свойствами (если не JValue) или с другим значением (если JValue), вам нужно вернуть обратно значение сравнения.

Имейте в виду следующее:

  • Метод сравнения должен быть рефлексивным, антисимметричным и транзитивным.
  • Контейнерные жетоны различного типа .Net необходимо упорядочить по типу в некотором последовательном порядке.
  • Приняты детские жетоны JArray и JConstructor.
  • нет токенов для детей 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.

+2

WOW! Это действительно отличная работа! Большое спасибо! То, что я делаю к настоящему времени, не так хорошо спроектировано. Надеюсь, я не забуду поставить 100 очков за два дня и надеюсь, что вы их получите. Но точки не имеют значения, мне повезло, потому что вы спасли меня днем! –

+0

Я новичок в SO, и случается, что возможность поставить +100 ушла после того, как я принял ответ ... –

+0

На данный момент сравнение не работает правильно при сравнении двух массивов строк с одинаковыми данными, но в разных заказ. Может быть, полезно для кого-то знать. –

0

Это может быть сделано с меньшим количеством кода. Не так хорошо, потому что с использованием сравнение строк вместо 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; 
    } 
Смежные вопросы