Home About
groovy, json

Java で JSON を diff したい → JSONassert が便利

Java を使って json データの差分を調べる必要が生じたので、便利なツールを探した。 Stackoverflow に JSONassert がよい、的な情報があったので、使ってみました。 とても便利だったので、紹介します。

基本の使い方

例によって Groovy script で記述しますが、こんな簡単に使えます。

@Grab(group='org.skyscreamer', module='jsonassert', version='1.5.0')
import org.skyscreamer.jsonassert.JSONAssert

def expected = '{"price": 100}'
def actual   = '{"price": 101}'

JSONAssert.assertEquals(expected, actual, false)

ここでは {"price": 100}{"price": 101} の2つの json 文字列の比較、差分検査です。 実行すると

java.lang.AssertionError: price
Expected: 100
     got: 101

100が期待されるところを 101 になっている ということで、メッセージもわかりやすい。

CustomComparator を使う

差分は知りたいが、 price については 値が異なっていても構わない という場合。

CustomComparatorRegularExpressionValueMatcher を使い price が数値でありさえすればOK を表現します。

@Grab(group='org.skyscreamer', module='jsonassert', version='1.5.0')

import org.skyscreamer.jsonassert.*
import org.skyscreamer.jsonassert.comparator.*

def expected = '{"price": 100}'
def actual   = '{"price": 101}'

def comparator = new CustomComparator(
    JSONCompareMode.LENIENT,
    new Customization('price', new RegularExpressionValueMatcher<Object>("\\d+")))

JSONAssert.assertEquals(expected, actual, comparator)

これを実行するとエラーは出ません、つまりテストにパスします。

もし、 {"price": 101}{"price": "100yen"} などに変更して実行すると:

java.lang.AssertionError: price: Constant expected pattern did not match value
Expected: \d+
     got: 100yen

このように、きちんと差があるとエラーを出してくれます。

自前の ValueMatcher を使う

price は 金額差が 10円以内ならば 差がないこととして扱いたい というような場合を考えます。

先ほどは RegularExpressionValueMatcher という既存に用意されていた ValueMatcher を使いましたが、 要は org.skyscreamer.jsonassert.ValueMatcher インタフェースを実装した自前 ValueMatcher を書けばよい。

@Grab(group='org.skyscreamer', module='jsonassert', version='1.5.0')

import org.skyscreamer.jsonassert.*
import org.skyscreamer.jsonassert.comparator.*

class MyValueMatcher implements ValueMatcher<Integer> {
    @Override
    boolean equal(Integer actual, Integer expected){
        return ( Math.abs(actual - expected) < 10 )
    }
}

def expected = '{"price": 100}'
def actual   = '{"price": 101}'

def comparator = new CustomComparator(
    JSONCompareMode.LENIENT,
    new Customization('price', new MyValueMatcher()))

JSONAssert.assertEquals(expected, actual, comparator)

これでテストパスします。

もし actual を {"price": 111} にすると:

java.lang.AssertionError: price
Expected: 100
     got: 111

のように差分ありのエラーがでます。

金額差の許容範囲をいろいろ変えたい場合は、 MyValuMatcher のコンストラクでその閾値を指定するなど。

なお CustomComparator に指定する Customization を複数登録したい場合は、 CustomComparator のコンストラクタに Customaization を複数列挙すればよい。このように:

def comparator = new CustomComparator(
    JSONCompareMode.LENIENT,
    new Customization('price', new MyValueMatcher<Integer>()),
    new Customization('tax', new MyValueMatcher<Integer>()))

まとめ

今日調べた範囲では、 ネストが深い json の比較・差分検出は難しいような感じですが、 その辺は自分で ネストを浅くした json を作って JSONassert していけばよいかと。