transform
은 내부 필드를 변환하거나 다른 객체에 매핑할 수 있게 해주는 패키지이다.
추가적인 변환기를 등록하는 것도 가능하다.
제네릭을 이용하기에 Go 1.18 이상의 버전이 필요하다.
transform
은 변환 명령어를 위해 태그를 사용한다. 기본 태그는 transform
이며 변경이 가능하다.
태그의 기본적인 형태는 아래와 같다:
type Person struct {
Name string `transform:"upper,bytes"`
Age int `transform:"x2,x:3,big"`
}
현재 태그를 Tag
메소드를 통해 확인할 수 있으며, SetTag
메소드를 통해 변경할 수 있다.
// declare transformer
transformer := transform.New()
// check tag (default: 'transform')
tag := transformer.Tag()
fmt.Println("tag is", tag)
// change tag
transformer.SetTag("sample")
// then now you only can use this transformer to parse 'sample' tag
// change tag when declare
transformer := transform.New().SetTag("hello")
태그 내의 명령어들은 콤마(,)로 구성된다.
만약 변환에 파라미터가 필요할 시 콜론(:)으로 키와 파라미터를 연결한다.
type Sample struct {
A int `transform:"add1"` // 'add1'이라는 변환기를 작동시킨다.
B int `transform:"add:1"` // 'add' 변환기에 '1' 파라미터를 넣어 작동시킨다.
C int `transform:"add1,add:1"` // 콤마를 통해 여러 내용을 넣을 수 있다.
}
RegisterTransformer
메소드를 통해 커스텀 변환기를 등록할 수 있다.
이 메소드는 변환기 이름과 변환 함수를 인자로 받는다.
변환 함수는 F1
과 F2
함수를 통해 구현할 수 있다.
F1
은 1개의 제네릭 타입을 받기에 입력과 출력 타입이 동일하다. 단순 내부 필드 변환을 위해 사용된다.
type Sample struct {
V1 int `transform:"add:10"`
V2 int `transform:"add:3"`
}
transformer.RegisterTransformer("add", transform.F1[int](func(i int, s string) int {
x, _ := strconv.ParseInt(s, 10, 32)
return i + int(x)
}))
위의 예시는 'add'라는 변환기를 추가하는 과정이다. 이것은 필드들을 입력받은 파라미터를 통해 업데이트한다.
F1
과 F2
함수의 두번째 인자는 태그에서의 파라미터이다.
F1
과 F2
의 유일한 차이점은 제네릭의 개수이다. F2
는 입력과 출력의 타입이 달라 매핑에 사용할 수 있다.
type Original struct {
V int `transform:"big"`
}
type Mapped struct {
V *big.Int
}
transformer.RegisterTransformer("big", transform.F2(func(i int, s string) *big.Int {
return big.NewInt(int64(i))
}))
초기화 할 때에도 변환기를 등록할 수 있다.
transformer := transform.New(
transform.I{
Name: "x2",
F: transform.F1(func(i int, s string) int {
return i * 2
}),
},
transform.I{
Name: "x",
F: transform.F1(func(i int, s string) int {
x, _ := strconv.ParseInt(s, 10, 32)
return i * int(x)
}),
},
)
Transform
은 객체 내부 필드를 변환하다. 에러 발생 시 에러를 반환한다.
type Sample struct {
S string `transform:"upper"`
}
sample := Sample{"hello"}
transformer := transform.New()
if err := transformer.Transform(&sample); err != nil {
panic(err)
}
fmt.Println(sample) // "HELLO"
Mapping
은 src
를 dst
로 매핑한다. 에러 발생 시 에러를 반환한다.
map
옵션을 통해 dst
의 필드를 타겟팅 할 수 있다. 점(.)을 이용해 내부 구조체에 대한 타겟팅도 가능하다.
하이픈(-)을 통해 매핑을 스킵할 수도 있다.
type Original struct {
F1 string
F2 string `transform:"map:F2.A,upper"`
F3 string `transform:"map:-"`
F4 int `transform:"F3"`
}
type Destination struct {
F1 string
F2 struct {
A string
}
F3 int
}
타입 변환 또한 가능하다.
type Original struct {
V int `transform:"big"`
}
type Mapped struct {
V *big.Int
}
transformer := transform.New()
transformer.RegisterTransformer("big", transform.F2(func(i int, s string) *big.Int {
return big.NewInt(int64(i))
}))
original := Original{10}
var mapped Mapped
if err := transformer.Mapping(&original, &mapped); err != nil {
panic(err)
}
fmt.Println(mapped) // {V: *big.Int(10)}
이 예제는 transform_test.go
에서도 확인 가능하다.
// dtom은 DTO Mapper를 의미한다.
type TransactionDtom struct {
Sender string `json:"sender" transform:"trim0x,lower"`
Amount string `json:"amount" transform:"big"`
}
// TransactionDtom은 여기로 매핑된다.
type TransactionDto struct {
Sender string
Amount *big.Int
}
a := transform.New()
a.RegisterTransformer("trim0x", transform.F2(func(s1, s2 string) string {
s1 = strings.TrimPrefix(s1, "0x")
s1 = strings.ToLower(s1)
return s1
}))
a.RegisterTransformer("big", transform.F2(func(s1, s2 string) *big.Int {
i := new(big.Int)
i.SetString(s1, 10)
return i
}))
// raw data -> [unmarshal] -> dtom -> [transform] -> dto
tx := `{
"sender":"0x4d943a7C1f2AF858BfEe8aB499fbE76B1D046eC7",
"amount":"436799733113079832970000"
}`
var transactionDtom TransactionDtom
err := json.Unmarshal([]byte(tx), &transactionDtom)
if err != nil {
panic(err)
}
var transactionDto TransactionDto
err = a.Mapping(&transactionDtom, &transactionDto)
if err != nil {
panic(err)
}
// {sender: 4d943a7c1f2af858bfee8ab499fbe76b1d046ec7, amount: 436799733113079832970000}
t.Log(transactionDto)
type TransactionEntity struct {
Id int `transform:"map:-"`
Sender string `transform:"add0x,map:From"`
Receiver string `transform:"add0x,map:To"`
Amount *big.Int `transform:"str"`
}
type TransactionResponse struct {
From string `json:"from"`
To string `json:"to"`
Amount string `json:"amount"`
}
a := transform.New()
a.RegisterTransformer("add0x", transform.F2(func(s1, s2 string) string {
return "0x" + s1
}))
a.RegisterTransformer("str", transform.F2(func(i *big.Int, s string) string {
return i.String()
}))
tx := &TransactionEntity{
Id: 12345,
Sender: "4d943a7c1f2af858bfee8ab499fbe76b1d046ec7",
Receiver: "fcba8de0706abf76e98d9ebeecbb42c29ab42ac3",
Amount: big.NewInt(436799733113079),
}
var transactionResponse TransactionResponse
err := a.Mapping(tx, &transactionResponse)
if err != nil {
panic(err)
}
// {"from":"0x4d943a7c1f2af858bfee8ab499fbe76b1d046ec7","to":"0xfcba8de0706abf76e98d9ebeecbb42c29ab42ac3","amount":"436799733113079"}
s, _ := json.Marshal(&transactionResponse)
t.Log(string(s))
go get github.com/p9595jh/transform
import "github.com/p9595jh/transform"