golang
[gin] ShouldBindJSON
bkdragon
2024. 9. 8. 15:08
gin 의 Context 에 있는 ShouldBindJSON 메서드는 JSON 데이터를 구조체로 바꿔주는 역할을 한다. 아마 요청을 읽고 Unmarshal 하는 과정이 있지 않을까 싶은데 실제 구현이 궁금해서 코드를 좀 찾아보았다.
func (c *Context) ShouldBindJSON(obj any) error {
return c.ShouldBindWith(obj, binding.JSON)
}
func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
return b.Bind(c.Request, obj)
}
ShouldBindJSON 는 ShouldBindWith 를 호출한다. ShouldBindWith 는 Binding 구조체와 obj를 받아서 Bind를 호출한다.
Binding 구조체를 살펴보자.
type Binding interface {
Name() string
Bind(*http.Request, any) error
}
인터페이스였다. Bind 메서드를 가지고 있다. 이제 구현체를 찾아보자.
var (
JSON BindingBody = jsonBinding{}
XML BindingBody = xmlBinding{}
Form Binding = formBinding{}
Query Binding = queryBinding{}
FormPost Binding = formPostBinding{}
FormMultipart Binding = formMultipartBinding{}
ProtoBuf BindingBody = protobufBinding{}
MsgPack BindingBody = msgpackBinding{}
YAML BindingBody = yamlBinding{}
Uri BindingUri = uriBinding{}
Header Binding = headerBinding{}
TOML BindingBody = tomlBinding{}
)
포맷에 따라 다양한 구현체가 구현되어있다. 아까 binding.JSON 을 ShouldBindWith 에 넘기고 있었는데 이 구현체를 넘긴것이였다.
jsonBinding 구조체를 확인해보면 Name과 Bind 메서드를 가지고 있을 것이다.
type jsonBinding struct{}
func (jsonBinding) Name() string {
return "json"
}
func (jsonBinding) Bind(req *http.Request, obj any) error {
if req == nil || req.Body == nil {
return errors.New("invalid request")
}
return decodeJSON(req.Body, obj)
}
func (jsonBinding) BindBody(body []byte, obj any) error {
return decodeJSON(bytes.NewReader(body), obj)
}
func decodeJSON(r io.Reader, obj any) error {
decoder := json.NewDecoder(r)
if EnableDecoderUseNumber {
decoder.UseNumber()
}
if EnableDecoderDisallowUnknownFields {
decoder.DisallowUnknownFields()
}
if err := decoder.Decode(obj); err != nil {
return err
}
return validate(obj)
}
빙고!
Name은 단순히 json이라는 문자열을 리턴해주고 있다. Bind는 예상과는 다르게 decoder 를 사용해서 스트림 방식으로 데이터를 처리한다. 이 방식은 메모리를 사용하지 않기 때문에 대용량 요청일 수록 성능적으로 이점이 있다.