概要
ある構造体の値を、別の構造体にマッピングしたいことがあると思います。
例えばインフラ層にはSELECT文を実行した結果をマッピングするための構造体Aがあって、それをドメイン層のモデルである構造体Bにマッピングして、さらにそれをクライアントに返すレスポンス用の構造体Cにマッピングして…
というように層ごとにデータ構造が変わる場合は適宜こうした操作が必要になります。
一般的にはこうした操作はヘルパー関数を作って対応されているのでしょうか。
自分はこれに対するベストプラクティスが分からないので、いい方法を知っていればぜひ教えていただきたいのですが、自分なりに samber/lo
を使ってやってみたので小ネタ的な紹介です。
ユースケース
2つ以上の構造体スライスの内容を1つの構造体スライスにマッピングしたいときなどです。
例えばあるメソッドを実行すると 構造体Userのスライス
と 構造体BlogPostのスライス
が返ってくるので、それらの内容を合わせたレスポンスを生成するために 構造体UserPostResponseのスライス
へマッピングするといったケースで以下のようなサンプルを作ってみました。
サンプル
package main
import (
"fmt"
"github.com/samber/lo"
)
type User struct {
Name string
Age int
}
type BlogPost struct {
Title string
Content string
}
type UserPostResponse struct {
Name string
Title string
Content string
}
func main() {
users, posts := getUserInfo()
res := lo.Map(
lo.Zip2(users, posts),
func(model lo.Tuple2[User, BlogPost], _ int) UserPostResponse {
user, post := model.A, model.B
return UserPostResponse{
Name: user.Name,
Title: post.Title,
Content: post.Content,
}
})
fmt.Printf("%+v\n", res)
}
func getUserInfo() ([]User, []BlogPost) {
users := []User{
{Name: "のび太", Age: 9},
{Name: "静香", Age: 9},
}
posts := []BlogPost{
{Title: "ドラえもんとの出会い", Content: "お正月に机の引き出しから..."},
{Title: "私の好きなもの", Content: "まずお風呂!食べ物は実はやき芋が..."},
}
return users, posts
}
下記が lo.Map
のシグネチャです。
func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R
何やら難しいですが
- 第一引数に任意の型Tのスライス
- 第二引数に任意の型Tの要素とインデックスを引数に取り、任意の型Rを返すコールバック関数
- コールバック関数の結果である任意の型Rのスライスが戻り値
次は lo.Zip2
のシグネチャです。
func Zip2[A, B any](a []A, b []B) []Tuple2[A, B]
- 第一引数に任意の型Aを要素とするスライス
- 第二引数に任意の型Bを要素とするスライス
- 各要素がA,B型のペアを持つTuple2構造体のスライスが戻り値
あとがき
サンプルに示した User
と BlogPost
のスライスの長さは同じである必要があります。
普通にfor文で回してそのインデックスでBlogPostを同時にマッピングさせることもできます。
res := make([]UserPostResponse, len(users))
for i, user := range users {
post := posts[i]
res[i] = UserPostResponse{
Name: user.Name,
Title: post.Title,
Content: post.Content,
}
}
sambler/lo
使って色々書いてきたけども、こっちの方がシンプルなのでいい気がしてきました🤣