仕事でGoで再帰関数を書いた時に苦戦したので備忘録として残しておきます。

サンプルコード

func unpackStruct(data interface{}) interface{} {
    switch d := data.(type) {
    case *structpb.Struct:
        _metadata := make(map[string]interface{})
        for k, x := range d.Fields {
            _metadata[k] = unpackStruct(x.GetKind())
        }
        return _metadata
    case *structpb.Value_StructValue:
        _metadata := make(map[string]interface{})
        for k, x := range d.StructValue.Fields {
            _metadata[k] = unpackStruct(x.GetKind())
        }
        return _metadata
    case *structpb.Value_ListValue:
        _metadata := make([]interface{}, len(d.ListValue.Values))
        for i, x := range d.ListValue.Values {
            _metadata[i] = unpackStruct(x.GetKind())
        }
        return _metadata
    case *structpb.Value_StringValue:
        return d.StringValue
    case *structpb.Value_NumberValue:
        return d.NumberValue
    case *structpb.Value_BoolValue:
        return d.BoolValue
    case *structpb.Value_NullValue:
        return d.NullValue
    default:
        return d
    }
}

内容としては、gRPCのgoogle.protobuf.Struct型をパースする処理です。
gRPCで受け取ったデータをDBに書き込む、といった処理はよくあると思うのですが、そのまま書き込むと取り出す時に非常に読みづらい形式になってしまうため取得時にパースしてやる必要があります。

こういった処理はライブラリがあると思うのですが、ぱっと探したところ無いように見えたので自力で書きました。

Golangで再帰関数を書く時の注意点

再帰関数を書く場合のユースケースとして引数に与えた型によって条件を分岐する場合があるかと思います。
pythonやJavascriptのような動的型付けの言語では気にする必要は無いのですが、Goのような静的型付けの言語では引数の型をしなければならず、かつ指定すると型に応じたメソッドを呼び出せない、というジレンマに陥ってしまいます。(単にGoビギナーなだけですが…)

この問題を解消する手段としてinterface型を引数に指定します(interface型は特殊な型でどのようなデータ型も許容できます)。

interface型として与えた引数を「switch d := data.(type)」のようにswitch文とキャストを組み合わせて処理することで、型に応じたメソッドが利用できるようになります。

bannerAds