go-cmpでmap[string]interface{}のJSONを比較する
GoでJSONを扱う際、型を定義せずに map[string]interface{}
を使いたくなることがあります。
var ( a = map[string]interface{}{ "data": map[string]interface{}{ "value": int64(1), }, } b = map[string]interface{}{ "data": map[string]interface{}{ "value": float64(1), }, } )
ちょっとした用途であれば特に問題ないかもしれませんが、テストで使おうとするとたまに数値のフィールドがfloat64とint64で比較できずに困ってしまいます。
(goldenファイルを読み込んだ場合など)
func TestReflect(t *testing.T) { if !reflect.DeepEqual(a, b) { t.Errorf("%v != %v", a, b) } }
こちらはint64が含まれている方をjson.Marshal
し、再度json.Unmarshal
することでfloat64にすることで回避できます。
func TestReflect2(t *testing.T) { tmp, err := json.Marshal(a) if err != nil { t.Fatal(err) } var got map[string]interface{} if err := json.Unmarshal(tmp, &got); err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, b) { t.Errorf("%v != %v", got, b) } }
ただ、なんだか無駄な変換をしているようでモヤモヤします。
モヤモヤするのであればきちんと型を定義するべきだとは思いますが、どうしてもstructを作りたくないことがあるかもしれません。
そんな時はgithub.com/google/go-cmp/cmpのFilterValuesを使用すると数値をfloat64として比較できます。
func TestCmpWithOpt(t *testing.T) { opt := cmp.FilterValues(func(x, y interface{}) bool { return isNumber(x) && isNumber(y) }, cmp.Comparer(func(x, y interface{}) bool { return cmp.Equal(toFloat64(x), toFloat64(y)) })) if !cmp.Equal(a, b, opt) { t.Errorf("%v != %v", a, b) } } func isNumber(v interface{}) bool { k := reflect.ValueOf(v).Kind() return k == reflect.Int64 || k == reflect.Float64 } func toFloat64(v interface{}) float64 { rv := reflect.ValueOf(v) if rv.Kind() == reflect.Int64 { return float64(rv.Int()) } return rv.Float() }
FilterValues
の第1引数には第2引数(opt
)を評価する条件となる関数を指定します。
mapのフィールドは全てinterface{}なのでxとyの型はinterface{}にする必要があります。
第2引数では実際に比較する関数を指定します。
このタイミングで数値をfloat64に変換して比較します。
なお、cmp.Comparer
のみだとcannot use an unfiltered option
でpanicしてしまいます。
全体のコードはこちらです。