

RubyでJSONデータを各要素の順番によらず、同じ要素が含まれていれば（連想配列の場合はキーと値の組み合わせが同じであれば）同一と判定したいと思っています。

以下のようなコードを考えていますが、もっとシンプルなやり方があれば教えてください。 （目的は同一判定なのでソート以外のやり方でも構いません）

[code]

    def json_sort(json)
      # jsonから生成したRubyオブジェクトを再帰的にソートする
      case json
      when Array
        # 配列の場合はソートし、json_sortを各要素に適用
        json.sort.map{|v|json_sort(v)}
      when Hash
        # 連想配列の場合はキーでソートした上でjson_sortを各要素に適用
        Hash[json.sort.map{|(k,v)|[k, json_sort(v)]}]
      else; json
      end
    end
    
    a = JSON.parse('{"a": 1, "b": [1, 2]}')
    b = JSON.parse('{"b": [2, 1], "a": 1}')
    json_sort(a) == json_sort(b) #=> 順番が違っても同一と判定したい
    
[/code]


CC BY-SA 3.0
2014-12-17T02:31:28.853
1841
2014-12-17T11:08:11.663
2213
question
ruby json
RubyでJSONを要素の順番によらず同一判定したい


参考情報を紹介します。  
stackoverflow に次のページがありました。

  * Canonicalizing JSON files <https://stackoverflow.com/questions/12584744/canonicalizing-json-files>

他にも net 上で "canonical json" を検索すると、類似の問題について情報が得られます。


CC BY-SA 3.0
2014-12-17T03:11:36.170
1851
2014-12-17T03:11:36.170
2017-05-23T12:38:55.250
-1
2987
1841
answer


スマホからなのでソースは少ししか示せませんが、

もともとHashを`==`で比較するときは順序が違っても比較してくれますよね。
ということは現状はArrayが問題で(配列の並びが違うものを同じとして良いかという議論はあるかもしれませんがそれは置いておいて)、JSONであれば複雑なオブジェクトも来ないだろうかと思います(そこの前提がだめだったらごめんなさい)。

そこで、もとのハッシュをディープコピーしたうえで、現在と同様中身を検査してArrayを破壊的なソートをしていくほうが、少しはシンプルになるようなきがします。

deep コピーは

[code]

    dup_a = Marshal.load(Marshal.dump(a))
    dup_b = Marshal.load(Marshal.dump(b))
    
[/code]

とできます。 そして配列だけソートして比較すれば

[code]

    dup_a["b"].sort! 
    dup_b["b"].sort!
    p dup_a==dup_b
    
[/code]

結果はtrueです。

ハッシュの中のすべての配列の並び変えを関数化すると、結局今と同じような再帰呼び出しとなってしまいますが、 再帰中のハッシュのソートと生成(when
Hash部分)の簡略化はできるんじゃないかと思います。


CC BY-SA 3.0
2014-12-17T03:45:33.443
1857
2014-12-17T03:45:33.443
728
1841
answer


Arrayを順番によらず同一視したいということは、RubyのArrayに展開されるのが間違っていると言う事だと思います。この場合はSetが望むものではないでしょうか？

array_classオプションで配列をどのクラスにするか指定できる(<http://docs.ruby-
lang.org/ja/2.1.0/method/JSON=3a=3aParser/s/new.html>)ので、それを使う事で以下のようにかけます。

[code]

    require 'set'
    a = JSON.parse('{"a": 1, "b": [1, 2]}',array_class: Set)
    b = JSON.parse('{"b": [2, 1], "a": 1}',array_class: Set)
    p a,b,a==b
    
[/code]

ただし、Setだと`[1,1,2]`が`[1,2]`と同じに扱われてしまうので、それだと不味い場合は、

[code]

    class CustomArray < Array
      def ==(a)
        self.sort == a.sort
      end
    end
    a = JSON.parse('{"a": 1, "b": [1, 2]}',array_class: CustomArray)
    b = JSON.parse('{"b": [2, 1], "a": 1}',array_class: CustomArray)
    
[/code]

とすると質問と同じ結果を出すコードになりますし、またはSetから入力をvalidateするクラスを作ってそれを用いるのもありえると思います。


CC BY-SA 3.0
2014-12-17T11:08:11.663
1915
2014-12-17T11:08:11.663
4372
1841
answer