💡

goverterを使って構造体間の詰替えが可能か検証

に公開

概要

Goでアプリケーションを開発していると構造体から構造体への詰替えは頻繁に発生すると思います。
特に項目数が多いと単調なコードを繰り返し記述することになり、微妙にしんどいなと感じています。

最近は生成AIがあるので多少はマシになったとはいえ、全てケースで正確に生成されるわけでもない為、決定的な解決策ではないと感じています。

この辺り、何か楽できるものはないかと思って調べてみました。
その中でgoverterというツールを見つけたので検証してみました。

公式サイト

https://goverter.jmattheis.de/

githubはこちら。
https://github.com/jmattheis/goverter

大雑把には変換したい2つの構造体を入力と出力に指定した関数を持つinterfaceを定義すると、そこから詰替え処理コードを生成してくれるツールになります。

使い方

以下の3ステップで使えるようになります。

1. インストール

まずは以下のようにinstallコマンドでinstallします。

go install github.com/jmattheis/goverter/cmd/goverter@v1.8.3

2. 変換処理のinterfaceを定義

次にこのツールのユニークな所だと思いますが、変換処理のinterfaceを定義します。
さらに定義したinterfaceにgoverter用のコメントを付与することで、goverterでの変換処理用のinterfaceであることを明示します。

具体的には以下のようなコードを記述することでInput1からOutput1へ変換処理をConverter1というinterfaceを満たす実装を生成します。

sample/sample1.go

package sample

type Input1 struct {
	Name string
	Age  int
}

type Oupput1 struct {
	Name string
	Age  int
}

// goverter:converter
type Converter1 interface {
	Convert(source Input1) Oupput1
}

3. 詰替え処理を生成

コマンドラインから以下のコマンドを実行します。

goverter gen ./sample/.

特に出力先などを指定しない場合、interfaceを定義したファイル直下にgeneratedというディレクトリが作成され、その中にgenerated.goというファイルが生成されます。
中身はこのようになっています。

sample/generated/generated.go

package generated

import sample "github.com/miyazi777/sample1/sample"

type Converter1Impl struct{}

func (c *Converter1Impl) Convert(source sample.Input1) sample.Oupput1 {
	var sampleOupput1 sample.Oupput1
	sampleOupput1.Name = source.Name
	sampleOupput1.Age = source.Age
	return sampleOupput1
}

Input1構造体のNameとAgeの詰替え処理を生成されています。

検証

ここからはいくつか、ありそうなシチュエーションを検証してみました。

無視したい項目がある時

項目の無視は可能。
入力側で無視したい項目がある場合は特に設定は不要。
出力側で無視したい項目がある場合は以下のサンプルのようにgoverter:ignore <項目名>のコメントを追記することで無視することが可能

以下のサンプルではInput2のGenderは無視され、Output2のAddressがignore指定されている為、無視されます。

package sample

type Input2 struct {
	Name   string
	Age    int
	Gender int
}

type Oupput2 struct {
	Name    string
	Age     int
	Address string
}

// goverter:converter
type Converter2 interface {
	// goverter:ignore Address
	Convert(source Input2) Oupput2
}

入力と出力で項目名が違う時

マッピング可能。

goverter:map <入力項目名> <出力項目名>を指定します。

以下サンプル。
下のサンプルではName -> UserNameに名前が違う項目への詰替えが実施されるコードが生成されました。

package sample

type Input3 struct {
	Name string
	Age  int
}

type Oupput3 struct {
	UserName string
	Age      int
}

// goverter:converter
type Converter3 interface {
	// goverter:map Name UserName
	Convert(source Input3) Oupput3
}

型が違う項目を変換したい時

マッピング可能。

goverter:map <入力項目名> <出力項目名> | <変換関数名>を指定する。

これは下のサンプルを見てもらった方がわかりやすいと思います。
int型のAgeからstring型のAgeに変換していて、変換関数にはConvertAgeを指定。

package sample

import "strconv"

type Input4 struct {
	Name string
	Age  int
}

type Oupput4 struct {
	Name string
	Age  string
}

// goverter:converter
type Converter4 interface {
	// goverter:map Age Age | ConvertAge
	Convert(source Input4) Oupput4
}

func ConvertAge(age int) string {
	return strconv.Itoa(age)
}

ネストした構造体を変換したい時

親子、それぞれの構造体を指定することで可能。

これもサンプルを見てもらった方がわかりやすいと思います。
Input5とOutput5、それぞれにDetailというネストした構造体が存在しています。
そして、親の変換処理である、Converter5と子の変換処理である、DetailConverter5を定義することで対応可能となります。

package sample

type Input5 struct {
	Name   string
	Age    int
	Detail Input5Detail
}

type Input5Detail struct {
	DetailName string
	DetailType int
}

type Output5 struct {
	Name   string
	Age    int
	Detail Output5Detail
}

type Output5Detail struct {
	DetailName string
	DetailType int
}

// goverter:converter
type Converter5 interface {
	Convert(source Input5) Output5
}

// goverter:converter
type DetailConverter5 interface {
	Convert(source Input5Detail) Output5Detail
}

非公開フィールドを持つ構造体

残念ながら対応していませんでした。

公式ページには非公開フィールドに関するページはあるのですが、これを読む限りは無視するか、コンストラクタがあるのであればコンストラクタを使用する、ということが案内されており、このツールで対応しないようでした。
https://goverter.jmattheis.de/guide/unexported-field

またissueでもgetterやsetterをサポートについての議論もあったのですが、特にこれがツールに反映されている様子はありませんでした。
https://github.com/jmattheis/goverter/issues/179

非公開フィールドへの対応まであったら、さらに使える幅が広がりそうなのに・・・

最後に

詰替え処理を楽にすることが出来る、1つの選択肢として良さそうなツールだと思いました。

Discussion