Go 언어 데이터 변환 및 직렬화 패턴

Go 언어에서 데이터 타입 간 변환과 JSON 스트림 처리는 실무에서 자주 마주치는 과제입니다. 본 문서에서는 uint32와 바이트 배열 간 변환, 불완전한 JSON 데이터 처리, 그리고 리소스 초기화 패턴을 다룹니다.

uint32와 []byte 상호 변환

바이너리 데이터를 처리할 때 부호 없는 정수와 바이트 슬라이스 간 변환은 기본적인 연산입니다. 다음 예제는 encoding/binary 패키지를 사용하여 Big Endian 방식으로 변환하는 방법을 보여줍니다.

package main

import (
	"encoding/binary"
	"fmt"
)

func main() {
	// uint32 값을 바이트 슬라이스로 변환
	original := uint32(305419896) // 0x12345678
	buf := make([]byte, 4)
	binary.BigEndian.PutUint32(buf, original)
	fmt.Printf("원본 uint32: %d, 바이트 슬라이스: %#v\n", original, buf)

	// 바이트 슬라이스를 uint32로 복원
	recovered := binary.BigEndian.Uint32(buf)
	fmt.Printf("복원된 uint32: %d\n", recovered)

	// 음수 값을 표현하려면 int32로 캐스팅
	negativeBytes := []byte{0xFF, 0xFF, 0xFF, 0xFF} // -1의 2의 보수
	asInt32 := int32(binary.BigEndian.Uint32(negativeBytes))
	fmt.Printf("바이트 슬라이스 %#v 는 int32로 %d 입니다\n", negativeBytes, asInt32)
}

불완전한 JSON 스트림 처리

직렬 포트나 네트워크 스트림에서 JSON 데이터를 읽을 때 첫 번째 줄이 불완전한 경우가 있습니다. bufio.Readerjson.Decoder를 조합하여 첫 번째 불완전한 줄을 건너뛰고 나머지 데이터를 처리할 수 있습니다. 아래 예제는 첫 줄이 깨진 JSON 스트림에서 유효한 레코드만 추출하는 방법을 보여줍니다.

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"strings"
)

type Message struct {
	Name string `json:"name"`
	Text string `json:"text"`
}

func main() {
	// 첫 번째 줄이 불완전한 JSON 스트림
	const jsonStream = `ft yourself!"}
	{"name": "Ed", "text": "Knock knock."}
	{"name": "Sam", "text": "Who's there?"}
	{"name": "Ed", "text": "Go fmt."}
`

	// bufio.Reader로 첫 줄을 읽어서 무시
	strReader := strings.NewReader(jsonStream)
	bufReader := bufio.NewReader(strReader)
	_, _, err := bufReader.ReadLine()
	if err != nil {
		panic("unable to skip first line: " + err.Error())
	}

	// json.Decoder로 스트리밍 디코딩
	jdec := json.NewDecoder(bufReader)
	jdec.DisallowUnknownFields() // 엄격한 JSON 검증

	for {
		var msg Message
		if err := jdec.Decode(&msg); err == io.EOF {
			fmt.Println("모든 유효한 JSON 레코드를 처리했습니다.")
			break
		} else if err != nil {
			fmt.Printf("디코딩 오류: %+v\n", err)
			return
		}
		fmt.Printf("%+v\n", msg)
	}
}

리소스 초기화 패턴

애플리케이션에서 직렬 포트와 같은 리소스를 사용할 때, 지연 초기화와 싱글톤 패턴을 결합하여 효율적으로 관리할 수 있습니다. 아래 예제는 MPort 구조체를 통해 직렬 포트 연결을 필요할 때만 생성하고 재사용하는 방법을 보여줍니다.

package main

import (
	"fmt"
	"sync"
	"time"

	"github.com/tarm/serial"
)

type MPort struct {
	Port *serial.Port
	mu   sync.Mutex
}

var (
	instance *MPort
	once     sync.Once
)

// newConn은 직렬 포트 연결을 생성합니다 (실제 구현은 환경에 맞게 수정)
func newConn() *serial.Port {
	c := &serial.Config{
		Name:        "COM1",
		Baud:        9600,
		ReadTimeout: time.Second,
	}
	port, err := serial.OpenPort(c)
	if err != nil {
		fmt.Printf("직렬 포트 열기 실패: %v\n", err)
		return nil
	}
	return port
}

// Write는 연결을 지연 초기화하고 데이터를 전송합니다
func (m *MPort) Write(buf []byte) (n int, err error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.Port == nil {
		m.Port = newConn()
		if m.Port == nil {
			return 0, fmt.Errorf("직렬 포트를 초기화할 수 없습니다")
		}
	}
	return m.Port.Write(buf)
}

// GetMPort는 싱글톤 인스턴스를 반환합니다
func GetMPort() *MPort {
	once.Do(func() {
		instance = &MPort{}
	})
	return instance
}

func main() {
	port := GetMPort()
	n, err := port.Write([]byte("Hello, serial!"))
	if err != nil {
		fmt.Printf("쓰기 실패: %v\n", err)
		return
	}
	fmt.Printf("%d 바이트 쓰기 성공\n", n)
}

이러한 패턴을 활용하면 바이너리 데이터 변환, 비정형 JSON 처리, 그리고 리소스 관리에서 코드의 안정성과 가독성을 높일 수 있습니다.

태그: go encoding/binary JSON bufio 직렬통신

6월 30일 02:36에 게시됨