質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.99%

【Perl】JSONデータの差分を取るには

受付中

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,119

nintail_fox

score 67

下記のようにJSONデータが2つあるとします。

JSON_A
[
  {
    'id' : '1',
    'name' : 'one',
    'age' : '18',
    'sex' : 'm'
  },
  {
    'id' : '2',
    'name' : 'two',
    'age' : '20',
    'sex' : 'f'
  },
  {
    'id' : '6',
    'name' : 'six',
    'age' : '5',
    'sex' : 'f'
  }
]
JSON_B
[
  {
    'id' : '2',
    'name' : 'two',
    'age' : '20',
  },
  {
    'id' : '1',
    'name' : 'one',
    'age' : '18',
    'sex' : 'm'
  },
  {
    'id' : '3',
    'name' : 'three',
    'age' : '60',
    'sex' : 'm'
  }
]

結果として、下記を取得したいです

{
    'id' : '2',
    'name' : 'two',
    'age' : '20',
  },
  {
    'id' : '3',
    'name' : 'three',
    'age' : '60',
    'sex' : 'm'
  }

要件としては

  • idをキーにして、データの重複を判定する
  • データの入っている順番は保証されない
  • JSON_Aから完全に重複するデータを削除し、差分のあるデータのみをJSON_Bから抽出したい

ex1) 下記は完全一致
A [{id=1,z=1,X=1},{id=2,z=2,x=2},{id=3,z=3,X=3}]
B [{id=3,z=3,X=3},{id=2,z=2,x=2},{id=1,z=1,X=1}]

ex2) 下記はid=1以外不一致
A [{id=1,z=1,X=1},{id=2,z=2,x=2},{id=3,z=3,X=3}]
B [{id=3,z=3},{id=2,z=222,x=222},{id=1,z=1,X=1},{id=4,z=4,x=4,c=4]

自分で考えてみたのですが、

  • JSON_AとJSON_Bを配列にデコード
  • 2つの配列をループ、その中で更にハッシュをループさせながらkeyとvalueを1つずつ比較する
  • 差分があるハッシュを配列にpushする

という感じで、ネストが深くあまりスマートでないのでスッキリ書く方法があれば教えて下さい。

追記:
自分が書いたコードです…。
aとbはJSONデコード済みと見て下さい。

my $diff_data;
my $diff_flag = 0;
my $id_match_flag = 0;
foreach my $b_data (@$b) {
  foreach my $a_data (@$a) {
    if ($b_data->{'id'} eq $a_data->{'id'}) {
      $id_match_flag = 1;

      if (keys(%$b_data) ne keys(%$a_data)) {
        push @$diff_data, $b_data;
      }
      else {
        foreach my $key (keys(%$b_data)){
          unless ($b_data->{$key} eq $a_data->{$key}) {
            $diff_flag = 1;
          }
        }
      }
      if ($diff_flag) {
        push @$diff_data, $b_data;
        $diff_flag = 0;
      }
    }
  }
  unless ($id_match_flag) {
    push @$diff_data, $b_data;
  }
  $id_match_flag = 0;
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

0

検索してみると以下のような説明ページがありました。
これらは参考になりますか?
https://blog.kteru.net/perl-array-sum-differentce-product/
http://d.hatena.ne.jp/tsucchi1022/20090419/1240152657
http://dev-man.seesaa.net/article/117885645.html
http://qiita.com/stm3/items/f5d825c5214a7848305a

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/03/24 18:43

    ありがとうございます。
    単純に配列の差分だけなら取れるのですが、
    配列の中のハッシュの重複…となるとご提示頂いた方法では使用できないようです。
    せっかくお調べ頂いたのにすみません。

    キャンセル

0

1件ずつCSVにして、1行に連結。(CSVでなくても)
そのご、Algorithm::Diff を使用して比較 
差分のCSVを JSONに変換

use Algorithm::Diff qw(diff);
use Data::Dumper;
use JSON;

my $json_A = '[{"id" : "1", "name" : "one", "age" : "18", "sex" : "m"}, {"id" : "2", "name" : "two", "age" : "20", "sex" : "f"}, {"id" : "6", "name" : "six", "age" : "5", "sex" : "f"}]';
my $data_A = decode_json($json_A);
my @line_A = ();
foreach my $item ( @$data_A ){
    my @csv = ( $$item{'id'}, $$item{'name'}, $$item{'age'}, $$item{'sex'});
    push @line_A, join( ',', @csv);
}

my $json_B = '[{"id" : "2", "name" : "two", "age" : "20"}, {"id" : "1", "name" : "one", "age" : "18", "sex" : "m"}, {"id" : "3", "name" : "three", "age" : "60", "sex" : "m"}]';
my $data_B = decode_json($json_B);
my @line_B = ();
foreach my $item ( @$data_B ){
    my @csv = ( $$item{'id'}, $$item{'name'}, $$item{'age'}, $$item{'sex'});
    push @line_B, join( ',', @csv);
}

@sort_line_A = sort {$a <=> $b} @line_A;
@sort_line_B = sort {$a <=> $b} @line_B;

@diffs = diff( \@sort_line_A, \@sort_line_B );

@out_json = ();
for my $dline ( @diffs ) {
    for my $d ( @$dline ) {
        if ( $d->[0] eq "+" ){
            #print $d->[0] . "\t" . $d->[2] . "--\n";
            my @jdata = split( ',', $d->[2]);
            my %item = ();
            $item{'id'}   = @jdata[0] if ( @jdata[0] );
            $item{'name'} = @jdata[1] if ( @jdata[1] );
            $item{'age'}  = @jdata[2] if ( @jdata[2] );
            $item{'sex'}  = @jdata[3] if ( @jdata[3] );
            push @out_json, \%item;

        }
    }
}
print encode_json( \@out_json ) . "\n";

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/03/28 11:28

    使い方が良くないのか、Algorithmだと細切れになりすぎて出来ませんでした…。

    キャンセル

  • 2016/03/28 15:09 編集

    ソースを張りました。
    json のkey value で連結すれば、もっとよいかも。

    キャンセル

0

初回答なので、空気が読めてないかも知れません。
terion さんの回答と似てますが、
このケースだと、普通に、ハッシュを文字列化して、
キャッシュと比較するだけで回答が得られます。

use Data::Dumper ;
# JSON => ARRAY 部分は省略し、@A, @B とする。
sub _make_key {
    my %h = %{$_[0]} ;
    return join ',', map{ $_, $h{$_} } sort keys %h ;
}

my %cache = map {+( _make_key $_ ), $_ } @A ;

print Dumper [ grep { ! defined $cache{ _make_key $_ } } @B ] ;

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/03/28 11:33

    試してみましたが、JSONが完全に一致しているかどうかしか得られませんでした…。

    キャンセル

  • 2016/03/28 21:49

    こちらの手元では正しい結果が得られます。
    失礼ですが、grep ブロックの中の !(ビックリマーク)を省略していませんか?

    キャンセル

0

たまたま同じような問題を検索していたらこのエントリーを発見したので書いておきます。

JSON::MergePatch - JSON Merge Patch implementation - metacpan.org

が、ドンピシャな感じでしょうか。

データ構造の差分をとるモジュールを使うと良さそうですかね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.99%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる