emahiro/b.log

日々の勉強の記録とか育児の記録とか。

go-cmp/cmpopts を使って map の特定の key のみをフィルタする

Overview

go-cmp を使って実際取得した値と期待値を比較するときに go-cmp/cmpopts を使って、二値の比較のオプションを設定することが出来ますが、この二値が map のときに map の中の特定の key に紐づく value のみを比較したいというユースケースがあって、既存の options だけでは満たせなかったのでやり方を考えました。

cmpopts.IgnoreMapEntries を利用する

IgnoreMapEntries を利用します。

IgnoreMapEntries は map 同士を比較、もしくは map と比較するときに特定の key-value のセットを ignore するという options になります。

discardFunc は interface を取っていますが、ここは func(k string, v any) bool という func 型を取り、指定した key を指定して返り値が true となる key-value のセットを ignore します。

繰り返しですが、このメソッドは func(k string, v any) bool の返り値が true のときの key-value のセットを ignore する、というオプションになるので、逆に false の場合は Ignore しない(=残る)というものになります。そのため、以下のようなメソッドを用意して特定の key-value のセットのみを残す(= フィルタする)ということが可能です。

// any のところは比較したい map の型定義に合わせる。例えば map[string]int 同士を比較したい場合は `want map[string]int` をとります。
func FilterWantMapEntires(want map[string]any) cmp.Option {
    return cmpopts.IgnoreMapEntries(func(k string, _ any) bool {
        _, ok := want[k]
        return !ok
    })
}

使い方としては以下のような感じで option の中に組み込むことを想定しています。

func main() {
    got := map[string]int{"1": 1, "2": 2, "3": 3}
    want := map[string]int{"1": 1, "2": 2} // 1 と 2 のみ比較したい。

    opts := []cmp.Option{
        FilterWantMapEntries(want),
    }

    if diff := cmp.Diff(got, want, opts...); diff != "" {
        fmt.Println(diff)
    }
}

ref: https://go.dev/play/p/4eU9w2nTgBp

Ref

あとから調べたのですが、go-cmp の cmpopts の使い方で実際にユースケースでよく使うものをベースに以下がよくまとまっていました。

zenn.dev