Contents

Introduction
Encoding
Decoding
Generic JSON with empty interface type
Decoding arbitrary data
Reference Types
Streaming Encoders and Decoders

Intoduction


JSON(JavaScript Object Notation)은 Key-Value 형태의 간단한 데이터 교환 방식으로, 문법적으로는 자바 스크립트의 객체와 리스트와 유사하다. JSON은 웹 백엔드와 클라이언트 사이의 통신에 가장 일반적으로 사용되지만 다른 많은 곳에서도 사용된다. Go에서는 json 패키지를 통해 쉽게 JSON 데이터를 읽고 쓸 수 있다.

Encoding


문자로 ‘a’라는 값은 정수 바이트 값으로 97이다. 메모리에서의 바이트는 해석하는 틀에 따라 다른데, 이러한 변환을 Encoding, Marshaling이라 한다.

JSON 데이터를 인코딩하기 위해서 Marshal 함수를 사용한다.

func Marshal(v interface{}) ([]byte, error)

다음과 같은 Message 라는 자료구조가 있고, m과 같은 Message의 인스턴스가 있다고 하자.

type Message struct{
	Name	string
	Body	string
	Time	int64
}

m := Message{"Alice", "Hello", 1294706395881547000}

위에서 설명한바와 같이 json.Marshal을 통해 JSON 인코딩으로 마샬링 할 수 있다.

b, err := json.Marshal(m)

아무 문제가 없다면, err는 nil일테고, b에는 Byte Slice 형식의 JSON 데이터가 들어간다. 인코딩 시, JSON으로 표시할 수 있는 데이터 구조만 인코딩 할 수 있다.

  • JSON 객체는 키로써 string만 지원한다. map 타입을 마샬링하려면 map[string]Type 형식이어야 한다.
  • Channel, Complex, 함수 타입은 인코딩 할 수 없다.
  • Cyclic 데이터 구조는 지원되지 않는다.
  • 포인터는 그 포인터가 가리키는 값으로 인코딩된다.(포인터가 nil이라면 null으로.)

json 패키지는 구조체 내부의 Exported 필드(대문자로 시작하는 public한 필드)에만 액세스한다.

Decoding


역으로 JSON 데이터를 디코딩하기 위해 Unmarshal 함수를 사용한다.

func Unmarshal(data []byte, v interface{}) error

디코딩된 데이터가 저장될 타입 변수를 선언하고 JSON 바이트 슬라이스와 변수에 대한 포인터를 인자로 json.Unmarshal을 호출한다.

var m Message
err := json.Unmarshal(b, &m)

만약 bm에 알맞는 데이터를 가지고 있다면, err는 nil이 되고 b로부터 디코딩된 데이터가 m 변수에 다음과 같이 할당했을때 처럼 올라온다.

m = Message{
	Name: "Alice",
	Body: "Hello",
	Time: 1294706395881547000,
}

이 때 Unmarshal 함수는 어떻게 디코딩된 데이터를 저장할 필드를 매핑할까? JSON 키 Foo에 대해서 Unmarshal은 다음과 같은 선호 순서대로 구조체 내의 대상 필드를 찾는다.

  • “Foo”로 태그된 Exported 필드.
  • “Foo”로 네이밍된 Exported 필드.
  • “FOO” 또는 “FoO” 처럼 “Foo”의 대소문자를 구분하지 않았을 때의 일치 항목.

Go 타입과 JSON 데이터가 정확히 매치되지 않았을 때는 어떤 일이 발생할까?

b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
var m Message
err := json.Unmarshal(b, &m)

Unmarshal은 구조체 내에서 찾을 수 있는 대상 타입에 대해서는 언마샬링한다. 위의 경우에서는 m의 Name 필드만 채워지고 Food 필드는 무시된다. 이 특성은 큰 JSON blob에서 특정한 필드들만 선택하려는 경우 유용하게 쓰인다. 또한 대상 구조체의 Unexported 필드는 Unmarshal의 영향을 받지 않는다.

하지만 JSON 데이터의 구조를 미리 알지 못할때에는 어떻게 될까?

Generic JSON with empty interface type


빈 인터페이스 타입(interface{})은 메서드가 없는 인터페이스이다. 빈 인터페이스 타입은 일반적인 컨테이너 타입으로 사용된다.

var i interface{}
i = "a string"
i = 2011
i = 2.777

타입 단언은 구체적인 타입에 접근하기 위해 사용된다.

r := i.(float64)
fmt.Println("the circle's area", math.Pi*r*r)

혹은 타입에 대해서 모를때에는, 타입 스위치가 타입을 결정한다.

switch v := i.(type) {
	case int:
    	fmt.Println("twice i is", v*2)
	case float64:
    	fmt.Println("the reciprocal of i is", 1/v)
	case string:
    	h := len(v) / 2
    	fmt.Println("i swapped by halves is", v[h:]+v[:h])
	default:
   		 // i isn't one of the types above
}

json 패키지는 map[string]interface{}와 []interface{} 값을 사용해 임의의 JSON 객체 및 배열을 저장한다. 이는 유효한 JSON blob을 interface{} 값으로 언마샬한다. 이에 대한 기본 타입은 다음과 같다.

  • bool <- boolean
  • float64 <- 숫자들
  • string <- 문자열
  • nil <- NULL

Decoding arbitrary data


아래와 같은 JSON 데이터가 변수 b에 저장되어 있다고 하자.

b := []byte(`{"Name":"Wednesday", "Age":6, "Parents":["Gomez", "Morticia"]}`)

이 데이터의 구조를 모르더라도 Unmarshal을 이용해 interface{} 값으로 디코딩 할 수 있다.

var f interface{}
err := json.Unmarshal(b, &f)

여기서 f에 들어있는 값은 키가 string이고 값은 빈 인터페이스의 값에 저장된 그 자체로의 값인 map 타입이 된다.

f = map[string]interface{}{
    "Name": "Wednesday",
    "Age":  6,
    "Parents": []interface{}{
        "Gomez",
        "Morticia",
    },
}

이 데이터 내부에 접근하기 위해서 f의 타입인 map[string]interface{}로 타입 단언을 사용한다.

m := f.(map[string]interface{})

그 다음 이 맵에 대해서 타입 스위치를 이용해 맵 내부의 값에 접근할 수 있다.

for k, v := range m {
    switch vv := v.(type) {
    case string:
        fmt.Println(k, "is string", vv)
    case float64:
        fmt.Println(k, "is float64", vv)
    case []interface{}:
        fmt.Println(k, "is an array:")
        for i, u := range vv {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(k, "is of a type I don't know how to handle")
    }
}

위와 같은 방법으로 타입에 대한 안정성을 지키면서 구조를 모르는 JSON 데이터에 우아하게 접근할 수 있는 것이다.

Reference Types


아래 코드에서도 우아한 매커니즘을 볼 수 있다.

type FamilyMember struct {
    Name    string
    Age     int
    Parents []string
}

    var m FamilyMember
    err := json.Unmarshal(b, &m)

m 변수에 FamiliyMember 구조체를 할당한 다음 해당 값에 대한 포인터를 Unmarshal 함수에 넘겼지만, 이 때 Parents 필드는 nil 슬라이스 값이었다. Parents 필드를 채우기 위해 Unmarshal은 뒤에서 새 슬라이스를 할당한다. 이는 Unmarshal이 지원되는 레퍼런스 타입(포인터, 슬라이스, 맵)에 지원되는 방식이다.

다음과 같은 데이터 구조를 언마샬링할 떄를 생각해보자.

type Foo struct{
	Bar *Bar
}

만약 JSON 객체에 Bar 필드가 있으면, Unmarshal은 새로운 Bar를 할당하고 값을 채울 것이고, 그렇지 않으면 Bar은 nil 포인터로 남을 것이다. 이로부터 유용한 패턴을 만들어낼 수 있다. 몇가지 특정한 메시지 타입을 받는 애플리케이션이 있다면, 다음과 같은 receiver 구조를 정의할 수 있다.

type IncomingMessage struct {
    Cmd *Command
    Msg *Message
}

이 메시지를 보내는 쪽에서는 통신하려는 메시지 유형에 따라 JSON 객체의 Cmd 필드님 Msg 필드를 채울 수 있다. Unmarshal은 JSON을 IncomingMessage 구조체로 디코딩 할 떄 JSON 데이터에 있는 데이터 구조에 대해서만 메모리를 할당하므로, 처리 할 메시지를 알아내기 위해서 Cmd 또는 Msg가 nil인지 아닌지만 테스트하면 된다.

Streaming Encoders and Decoders


json 패키지는 JSON 데이터 스트림을 IO 하는 일반적인 작업을 지원하기 위해서 DecoderEncoder 타입을 제공한다. NewDecoder와 NewEncoder 함수는 io.Readerio.Writer 인터페이스 유형을 래핑한다.

func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder

다음인 표준 입력에서 일련의 JSON 객체를 읽고 각 객체에서 Name 필드를 제외한 모든 객체를 제거한 다음 표준 출력에 쓰는 예제 프로그램이다.

package main

import (
    "encoding/json"
    "log"
    "os"
)

func main() {
    dec := json.NewDecoder(os.Stdin)
    enc := json.NewEncoder(os.Stdout)
    for {
        var v map[string]interface{}
        if err := dec.Decode(&v); err != nil {
            log.Println(err)
            return
        }
        for k := range v {
            if k != "Name" {
                delete(v, k)
            }
        }
        if err := enc.Encode(&v); err != nil {
            log.Println(err)
        }
    }
}

Reader와 Write의 재사용성 덕분에 이러한 Encoder와 Decoder 타입은 HTTP 연결, WebSocker 또는 파일 IO와 같은 다양한 시나리오에서 사용할 수 있다.