ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ Golang ] Rest Api 스펙이 틀릴때 (타입이 틀릴때) Json decode(Unmarshal) 하기
    개발/golang 2021. 4. 2. 21:08

    외부 혹은 내부 Rest API 를 사용할때 문서에 적혀있는 스펙과 다른 타입을 반환할때가 종종있다.

    이럴때 그냥 그대로 json decode (unmarshal) 를 하게될경우 err 가 발생하는것을 확인할수 있다.

    따라서 아래와 같이 예방할수있다.

    package example_wrong_type_api_spec
    
    import (
    	"encoding/json"
    	"strconv"
    	"strings"
    	"time"
    )
    
    type ProductResponseSpec struct {
    	Name      string    `json:"name"`
    	Price     int       `json:"price"`
    	CreatedAt time.Time `json:"created_at"`
    }
    
    type ProductResponseSpecProtect struct {
    	Name      string   `json:"name"`
    	Price     SafeInt  `json:"price"`
    	CreatedAt SafeTime `json:"created_at"`
    }
    
    type SafeTime struct {
    	time.Time
    }
    type SafeInt struct {
    	Value int
    }
    
    func (m *SafeInt) UnmarshalJSON(data []byte) error {
    	strData := string(data)
    	if strData == "null" || strData == `""` {
    		return nil
    	}
    	n, err := strconv.Atoi(strings.ReplaceAll(strData, "\"", ""))
    	*m = SafeInt{n}
    	return err
    }
    
    func (m *SafeTime) UnmarshalJSON(data []byte) error {
    	if string(data) == "null" || string(data) == `""` {
    		return nil
    	}
    	tt, err := time.Parse(`"`+time.RFC3339+`"`, string(data))
    	*m = SafeTime{tt}
    	return err
    }
    
    type HttpClient interface {
    	Request() string
    }
    type HttpResponse struct {
    	StatusCode int
    	Url        string
    	Body       string
    }
    
    type ProductProxyAPI struct {
    	h HttpClient
    }
    
    func NewProductProxyAPI(h HttpClient) ProductProxyAPI {
    	return ProductProxyAPI{h: h}
    }
    
    func (p ProductProxyAPI) Search() (ProductResponseSpec, error) {
    	response := p.h.Request()
    	spec := ProductResponseSpec{}
    	if err := json.Unmarshal([]byte(response), &spec); err != nil {
    		return ProductResponseSpec{}, err
    	}
    	return spec, nil
    }
    
    func (p ProductProxyAPI) SafeSearch() (ProductResponseSpecProtect, error) {
    	response := p.h.Request()
    	spec := ProductResponseSpecProtect{}
    	if err := json.Unmarshal([]byte(response), &spec); err != nil {
    		return ProductResponseSpecProtect{}, err
    	}
    	return spec, nil
    }
    
    
    
    package example_wrong_type_api_spec_test
    
    import (
    	"github.com/golang/mock/gomock"
    	"github.com/hoyeonUM/golang-example/example_wrong_type_api_spec"
    	mock_example_wrong_type_api_spec "github.com/hoyeonUM/golang-example/example_wrong_type_api_spec/mock"
    	"github.com/stretchr/testify/assert"
    	"testing"
    	"time"
    )
    
    var response = `
    {
    	"name" : "test product",
    	"price" : "",
    	"created_at" : ""
    }
    `
    
    func TestAPI(t *testing.T) {
    	t.Run(`given wrong type response when use non safe struct json unmarshal then return error`, func(t *testing.T) {
    		ctrl := gomock.NewController(t)
    		defer ctrl.Finish()
    		m := mock_example_wrong_type_api_spec.NewMockHttpClient(ctrl)
    		m.EXPECT().Request().Return(response).Times(1)
    		api := example_wrong_type_api_spec.NewProductProxyAPI(m)
    		_, err := api.Search()
    		assert.Error(t, err)
    	})
    
    	t.Run(`given wrong type response when use safe struct json unmarshal then return struct and nil`, func(t *testing.T) {
    		ctrl := gomock.NewController(t)
    		defer ctrl.Finish()
    		m := mock_example_wrong_type_api_spec.NewMockHttpClient(ctrl)
    		m.EXPECT().Request().Return(response).Times(1)
    		api := example_wrong_type_api_spec.NewProductProxyAPI(m)
    		result, err := api.SafeSearch()
    		assert.Nil(t, err)
    		assert.Equal(t, "test product", result.Name)
    		assert.Equal(t, 0, result.Price.Value)
    		assert.Equal(t, time.Time{}.Format(time.RFC3339), result.CreatedAt.Format(time.RFC3339))
    	})
    }
    
    
    

     

Designed by Tistory.