Files
2025-11-17 23:25:36 +09:00

147 lines
5.3 KiB
Markdown

# Go 언어와 JSON 다루기
Go 언어에서 JSON 데이터를 다루는 방법을 배워봅시다.
Go 표준 라이브러리에는 JSON 데이터를 처리하기 위한 `encoding/json` 패키지가 포함되어 있습니다. Go는 구조체 태그(Struct Tag)를 사용하여 Go 구조체와 JSON 필드를 손쉽게 매핑할 수 있는 기능을 제공합니다. 이 태그는 Go 구조체를 JSON으로 변환하거나, JSON을 Go 구조체로 변환하는 과정을 제어합니다. 이 과정을 각각 Marshaling과 Unmarshaling이라고 부릅니다.
## `Marshal()`과 `Unmarshal()` 이해하기
**Marshaling**과 **Unmarshaling**은 Go 구조체로 JSON 데이터를 다룰 때 핵심적인 과정입니다.
- **Marshaling**: Go 구조체(메모리 상의 데이터)를 JSON 문자열(텍스트 데이터)로 변환하는 과정입니다. 주로 API 응답으로 JSON을 보내거나, 데이터를 파일로 저장할 때 사용됩니다.
- **Unmarshaling**: JSON 문자열을 Go 구조체로 변환하는 과정입니다. 주로 외부 API로부터 받은 JSON 데이터를 다루거나 파일에서 데이터를 읽어올 때 사용됩니다.
> **가장 흔히 겪는 문제**: JSON과 Go 구조체 간 변환 시 가장 흔한 버그는 구조체의 필드명을 소문자로 시작하여 발생하는 문제입니다. `encoding/json` 패키지가 구조체의 필드에 접근하려면, 해당 필드는 **반드시 대문자로 시작해야 합니다 (Exported field)**. Marshaling 또는 Unmarshaling이 제대로 동작하지 않는다면, 가장 먼저 구조체 필드명이 대문자로 시작하는지 확인해 보세요.
# 코딩 예제
아래 `encodeDecode.go` 코드는 간단한 예제를 통해 JSON 레코드의 Marshaling과 Unmarshaling 과정을 보여줍니다.
```go
package main
import (
"encoding/json"
"fmt"
)
// UseAll 구조체는 JSON 데이터와 매핑됩니다.
type UseAll struct {
Name string `json:"username"`
Surname string `json:"surname"`
Year int `json:"created"`
}
```
구조체 필드 옆의 `` `json:"..."` `` 부분을 **구조체 태그**라고 부릅니다. 이 태그는 각 필드가 JSON 데이터에서 어떤 키(key)와 매핑되는지를 명시합니다.
- `Name` 필드는 JSON에서 `username` 키와 매핑됩니다.
- `Surname` 필드는 `surname` 키와 매핑됩니다.
- `Year` 필드는 `created` 키와 매핑됩니다.
이 태그 정보는 Marshaling과 Unmarshaling 과정에서 사용되며, 이 외에는 `UseAll`을 일반적인 Go 구조체처럼 사용하면 됩니다.
```go
func main() {
// Marshaling할 구조체 인스턴스 생성
useall := UseAll{Name: "Mike", Surname: "Tsoukalos", Year: 2021}
// Go 구조체를 JSON 바이트 슬라이스로 Marshaling합니다.
t, err := json.Marshal(&useall)
}
```
`json.Marshal()` 함수는 Go 데이터(주로 구조체 포인터)를 인자로 받아, JSON으로 인코딩된 `[]byte`와 `error`를 반환합니다.
```go
if err != nil {
fmt.Println(err)
} else {
// t는 []byte 타입이므로, 출력을 위해 문자열로 변환합니다.
fmt.Printf("Value %s\n", t)
}
// Unmarshaling할 JSON 문자열 데이터
str := `{"username": "M.", "surname": "Ts", "created":2020}`
```
JSON 데이터는 보통 문자열 형태로 다루어집니다.
```go
// json.Unmarshal 함수는 바이트 슬라이스를 인자로 받으므로, 문자열을 변환합니다.
jsonRecord := []byte(str)
```
`json.Unmarshal()` 함수는 바이트 슬라이스 (`[]byte`)를 인자로 받기 때문에, 먼저 JSON 문자열을 `[]byte` 타입으로 변환해야 합니다.
```go
// 변환된 JSON 데이터를 담을 구조체 변수를 선언합니다.
var temp UseAll
// JSON 바이트 슬라이스를 Go 구조체로 Unmarshaling합니다.
err = json.Unmarshal(jsonRecord, &temp)
```
`json.Unmarshal()` 함수는 JSON 데이터가 담긴 바이트 슬라이스와, 데이터를 채워 넣을 Go 구조체 변수의 **포인터**를 인자로 받습니다. 포인터를 사용하는 이유는 함수가 `temp` 변수의 값을 직접 수정해야 하기 때문입니다. 함수가 종료된 후에도 변경된 값이 유지되려면 이처럼 변수의 메모리 주소를 전달해야 합니다.
```go
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Data type: %T with value %v\n", temp, temp)
}
```
`encodeDecode.go`를 실행하면 다음과 같은 결과가 출력됩니다.
```bash
# go run encodeDecode.go
Value {"username":"Mike","surname":"Tsoukalos","created":2021}
Data type: main.UseAll with value {M. Ts 2020}
```
---
### 전체 예제 코드
```go
package main
import (
"encoding/json"
"fmt"
)
type UseAll struct {
Name string `json:"username"`
Surname string `json:"surname"`
Year int `json:"created"`
}
func main() {
useall := UseAll{Name: "Mike", Surname: "Tsoukalos", Year: 2021}
// Marshaling: Go 구조체 -> JSON
t, err := json.Marshal(&useall)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Value %s\n", t)
}
// Unmarshaling할 JSON 문자열
str := `{"username": "M.", "surname": "Ts", "created":2020}`
jsonRecord := []byte(str)
// 결과를 저장할 구조체 변수
var temp UseAll
// Unmarshaling: JSON -> Go 구조체
err = json.Unmarshal(jsonRecord, &temp)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Data type: %T with value %v\n", temp, temp)
}
}
```