対処方法
package main
import (
"fmt"
"github.com/jinzhu/copier"
)
// 変更してはいけない START
type Domain struct {
Name string
Age int
Weight float32
BodyHeight float32
}
type OutSider struct {
Name string
Age int64
BodyWeight float64
Height *OutSiderHeight
}
type OutSiderHeight struct {
Height float64
BodyHeight float64
}
// 変更してはいけない END
// ポイントはコピー先と全く同じ名前、型の構造体にすること
type Middle struct {
Name string
Age int
Weight float32 `copier:"BodyWeight"`
BodyHeight float32 `copier:"-"`
}
func(m *Middle) Height(h *OutSiderHeight) {
if h != nil {
m.BodyHeight = float32(h.BodyHeight)
}
}
func main() {
var err error
outSider := &OutSider{
Name: "sasakuna",
Age: 30,
BodyWeight: 23.95,
Height: &OutSiderHeight{
Height: 168.9,
BodyHeight: 99.999,
},
}
domain1 := Domain{}
domain2 := Domain{}
// このままだと`Weight`と`BodyHeight`がコピーできない
err = copier.Copy(&domain1, outSider)
fmt.Println("Domain: ", domain1)
fmt.Println("Error: ", err)
// 構造体をほかのものと見せかけることでタグを外付けできる
err = copier.Copy((*Middle)(&domain2), outSider)
fmt.Println("Domain: ", domain2)
fmt.Println("Error: ", err)
}
もう少し詳しく
対処できないケースがあります
以下のケースでは、Domain
のHeight
がコピーされません。
ポイントは、変更してはいけない構造体間で同じ名前なのにcopierでは自動的に変換できないケースです。
特に記載はしませんが一応二つ中間構造体を用意し、Copy
を二回行えばできなくはないです。
package main
import (
"fmt"
"github.com/jinzhu/copier"
)
// 変更してはいけない START
type Domain struct {
Name string
Age int
Weight float32
Height float32
}
type OutSider struct {
Name string
Age int64
BodyWeight float64
Height *OutSiderHeight
}
type OutSiderHeight struct {
Height float64
BodyHeight float64
}
// 変更してはいけない END
type Middle struct {
Name string
Age int
Weight float32 `copier:"BodyWeight"`
Height float32 `copier:"Height"`
}
func main() {
var err error
outSider := &OutSider{
Name: "sasakuna",
Age: 30,
BodyWeight: 23.95,
Height: &OutSiderHeight{
Height: 168.9,
BodyHeight: 99.999,
},
}
domain1 := Domain{}
domain2 := Domain{}
err = copier.Copy(&domain1, outSider)
fmt.Println("Domain: ", domain1)
fmt.Println("Error: ", err)
err = copier.Copy((*Middle)(&domain2), outSider)
fmt.Println("Domain: ", domain2)
fmt.Println("Error: ", err)
}
さらに詳しく
内容自体は以上となっていますが、なぜこのようなことをしようとしたかについて説明していきたいと思います。
自動生成コードと依存関係問題の板挟み
依存方向を統一したコードを書こうとすると、必ずデータのパース処理というものが発生してくるかと思います。
その場合、まず変換を行うような層であれば、ドメインを変換することは基本的に許されないかと思います。またこれを許可した場合であっても、今回の例でいうところのOutSider
にあたる外部の構造体は複数存在することが予測され柔軟に対応することはできません。
ならばOutSiderをと思っても、これらは大抵外部のライブラリで変更することはかないません。(今回はGrpcを使ってみていたのですが、これも自動生成なので変更は容易ではありません。)
そこで、今回はGoの名前の違う構造体であっても、名前と型が一致していれば直接変換可能な特性を生かして可能な限り簡単に変換できる方法を提案してみました。
実際のところ
とはいえ今回の手法は、構造体のネストがあまり深くないからこそ実現できたものです。
結局、ネストが深いと手間がかかることは避けられなそうなのですが、そのあたりの調査はまた別の機会に行いたいと思います。