Go 객체 변환기

박재훈·2022년 11월 22일
0

GO

목록 보기
7/23
post-custom-banner

Description

transform은 내부 필드를 변환하거나 다른 객체에 매핑할 수 있게 해주는 패키지이다.
추가적인 변환기를 등록하는 것도 가능하다.
제네릭을 이용하기에 Go 1.18 이상의 버전이 필요하다.

Tag

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"` // 콤마를 통해 여러 내용을 넣을 수 있다.
}

Register

RegisterTransformer 메소드를 통해 커스텀 변환기를 등록할 수 있다.
이 메소드는 변환기 이름과 변환 함수를 인자로 받는다.
변환 함수는 F1F2 함수를 통해 구현할 수 있다.

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'라는 변환기를 추가하는 과정이다. 이것은 필드들을 입력받은 파라미터를 통해 업데이트한다.
F1F2 함수의 두번째 인자는 태그에서의 파라미터이다.

F1F2의 유일한 차이점은 제네릭의 개수이다. 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

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

Mappingsrcdst로 매핑한다. 에러 발생 시 에러를 반환한다.
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)}

DTO example

이 예제는 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)

Entity to Response example

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))

How to use

go get github.com/p9595jh/transform
import "github.com/p9595jh/transform"
profile
생각대로 되지 않을 때, 비로소 코딩은 재미있는 법.
post-custom-banner

0개의 댓글