In computer science, marshalling or marshaling (US spelling) is the process of transforming the memory representation of an object into a data format suitable for storage or transmission. It is typically used when data must be moved between different parts of a computer program or from one program to another. wikipedia

마셜링이란 데이터를 전송이나 보관에 유리한 방식으로 변경하는 것을 말한다. 컴퓨터에 유리한 방식이니 당연히 byte형태의 어떤 데이터의 나열이 될 것이고, 그렇다면 마셜링의 반대 방향, 언마셜링은 byte 형식에서 다른 형태로 바뀌는 것을 말한다.

바이트는 컴퓨터에서 사용하는 값이다. 바이트를 어떤 형태의 데이터로 보는가를 정하는 것은, 그 값이 어떠한 타입에 담기는가에 따라 다르다. 예를 들어 바이트 0x41, 65는 int 타입이라면 숫자 65가 되고 char 타입에서 A로 인식한다. 따라서 마셜링이라는 것은 어떤 데이터 타입에서 byte로의 일방향 변환이지만, 언마셜링은 한가지 방향으로 진행되지 않는다. 즉 언마셜링할 때 어떤 데이터 타입으로 변환해야 할지를 지정해 줄 필요가 있다. 마셜링의 방향은 byte라면 언마셜링의 방향은 유저의 의도가 담겨 있다고나 할까.

JSON으로, JSON으로의 변환

json은 키와 값의 매핑이 나열되어 있는 텍스트라고 볼 수 있다. 키가 있으면 그에 해당하는 값이 있다. 값의 종류는 다시 값이 될 수도 있으며, 다시 다른 값을 가리키는 키가 될 수도 있다. 그리고 Go에서도 이런 형태의 내부 구조를 가진 타입이 있는데 이를 struct, 구조체라고 부른다.

구조체는 값을 가리키는 필드들의 모임으로 정의된다. 필드는 변수이며, 변수를 라고 하면 변수에 담긴 값을 통해 이를 키와 값의 매핑으로 볼 수 있다. 그래서 구조체는 json과 굉장히 흡사한 모양을 가진다.(사실 생각해보면 json이 자바스크립트의 오브젝트 표현식이므로 크게 다르진 않을 것이다). 어쨌든 이런 흡사한 모습 때문에 구조체는 json으로 변환이 가능하다. 따라서 json을 string, 그러니까 []byte의 형태로 본다면, 구조체를 마셜링하여 []byte형태로 바꾸는 것이 가능하고, []byte를 마찬가지로 언마셜링하여 구조체로 다시 변경할 수 있다.

marshal-unmarshal

그림상으로는 []byte에 대한 이야기가 빠졌는데 string과 []byte가 그냥 같다고 생각하자. 어쨌든, 이렇게 하면 구조체와 json간 상호 변환이 가능하다. 그리고 이는 위에서 이야기한 마셜링과 언마셜링의 뜻과 딱 맞아떨어진다. 데이터를 byte형태로 변환하였으니까 말이다. 구조체에 담긴 다양한 값들을 []byte로 변환한 것이다.

json.Marshal

어떤 json을 []byte 타입으로 변경하기 위해서 encoding/json 패키지의 Marshal 함수를 사용할 수 있다.

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

위에서 계속 이야기한 대로 마셜링은 데이터를 []byte의 형태로 변경하는 것이 목표이므로 이 함수의 리턴은 마셜링된 데이터 []byte를 리턴한다. 그리고 변환에 실패한 경우를 위해 error도 같이 리턴한다. 파라미터 v는 인터페이스 형태인데, 이는 어떠한 값이 파라미터로 들어오더라도 json 형태로 변경한다는 뜻이다. 즉 이 파라미터는 꼭 구조체일 필요가 없다. 음? json이려면 키와 값이 있어야하지 않나? 하는데, go의 패키지 jsonRFC 7159구현하였다고 명시되어 있으며, RFC 7159의 내용은 간략히 말하면 이와 같다

JSON-text = ws value ws

ws는 빈 칸이나 줄바꿈 같은 캐릭터를 이야기하는 거고, value는 true, false, null, object, array, number, string이다. 따라서 단순 숫자만 있다고 해도 이건 json이며 따라서 json.Marshal의 파라미터로 구조체 뿐 아니라 일반 변수도 Marshalling 가능하다. 그럼 한번 간단히 json을 마셜링해 보자.

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    a, _ := json.Marshal(1)
    b, _ := json.Marshal("1")
    c, _ := json.Marshal([]int{1,2,3,4,5})
    d, _ := json.Marshal([]string{"abcd", "efgh", "ijkl"})

    fmt.Println("a? ", a, string(a))
    fmt.Println("b? ", b, string(b))
    fmt.Println("c? ", c, string(c))
    fmt.Println("d? ", d, string(d))
}

실행 결과는 다음과 같다.

$ go run main.go
a?  [49] 1
b?  [34 49 34] "1"
c?  [91 49 44 50 44 51 44 52 44 53 93] [1,2,3,4,5]
d?  [91 34 97 98 99 100 34 44 34 101 102 103 104 34 44 34 105 106 107 108 34 93] ["abcd","efgh","ijkl"]

값이 그대로 json의 number나 string, array로 바뀌어서 이게 byte형태로 저장된 것을 확인할 수 있다. 뭔가 변환이 되거나 해서 그러는게 아니라 단순히 그 바이트를 그대로 변환하는 것이다. 그래서 그 []byte를 그대로 string형태로 바꾸면 json이 출력된다. json.Marshal이 하는 일이 이게 다다. 변수를 그대로 json의 Value로 바꾸고 이를 byte형태로 변경하는 것.

그러면 이번에는 구조체의 Marshal을 생각해 보자. 구조체는 어떻게 하면 Marshal할 수 있을까? 구조체는 값을 담는 필드가 모여 있는 타입이므로, 결국 구조체의 marshal이라는 것은 각 구조체의 필드마다 전부 Marshal을 진행하면 된다. 구조체는 결국 object이므로 {로 시작해서 object안의 필드를 깊이든 너비든 순회하며 마셜링하고 마지막에 }로 닫아주면 Marshaling이 끝인 것.

이번에는 []byte을 어떻게 Marshal할 것인가? 값을 byte로 바꾸는 것이 마셜링인데 이미 바이트라면? 이는 base64로 인코딩하여 처리한다.

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    a := struct {
        Name string
        Data []byte
    }{
        Name: "Byte data type",
        Data: []byte{1, 2, 3, 4, 5},
    }

    b, _ := json.Marshal(a)
    fmt.Println("a?: ", string(b))
}
// https://play.golang.org/p/T5IQhqLrBhP
// a?:  {"Name":"Byte data type","Data":"AQIDBAU="}

이번엔 json의 키워드 중 null을 생각해 보자. Go는 변수가 선언되면 무조건 초기값이 들어가는 것이 보장되므로, 어떠한 변수를 선언하더라도 항상 값이 들어가 있는 상태가 된다. string이라면 빈 문자열 "", 숫자라면 0, bool 타입이라면 false… 그러면 null은 어떻게 넣을 수 있는가? null은 마셜링 대상 타입이 포인터 타입일 경우에 처리할 수 있다.

아래에는 json의 각 value에 따라서 대강 이런 느낌으로 매핑되는 것들을 간단히 코드로 작성했는데, d의 경우 슬라이스를 선언만 하는 경우 nil이므로 :=를 통해 초기화를 진행했다.

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var a int            // number
	var b string         // string
	var c bool           // false
	d := []interface{}{} // array
	var e struct{}       // object
	var f *int           // null

	result_int, _ := json.Marshal(a)
	result_string, _ := json.Marshal(b)
	result_bool, _ := json.Marshal(c)
	result_array, _ := json.Marshal(d)
	result_object, _ := json.Marshal(e)
	result_null, _ := json.Marshal(f)

	fmt.Println("a? ", a, string(result_int))
	fmt.Println("b? ", b, string(result_string))
	fmt.Println("c? ", c, string(result_bool))
	fmt.Println("d? ", d, string(result_array))
	fmt.Println("e? ", e, string(result_object))
	fmt.Println("f? ", f, string(result_null))
}
// a?  0 0
// b?   ""
// c?  false false
// d?  [] []
// e?  {} {}
// f?  <nil> null
// https://play.golang.org/p/PmuIu8dk-1c

json.Unmarshal

어떤 []byte, 사실상 string 형태의 데이터가 있는 경우 이를 json형태로 되돌릴 수 있다. 이를 Unmarshaling, 언마셜이라고 하며 Go에서는 encoding/json 패키지의 Unmarshal함수를 통해서 처리할 수 있다.

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

언마샬링은 위에서 이야기한 대로 어떤 byte값을 타입에 적용하는 것이다. 같은 바이트값이라 하더라도 그 값이 담긴 타입에 따라 처리가 달라진다. 그런 면에서 이 함수를 생각해 보면 결국 Unmarshal이란 것은 datav타입에 적용시키는 것이다. 그게 불가능하다면 error를 리턴하는 것이고. 그래서 이 함수는 에러만 리턴한다. 즉 결과물이 없고 파라미터로 들어온 v를 직접 변경시킨다.

v를 변경시키기 위해 이 v는 반드시 포인터 타입이어야 한다. 그게 아니면 값 복사니까.

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    var a int
    json.Unmarshal([]byte("65"), a) //call of Unmarshal passes non-pointer as second argument

    fmt.Println("a?: ", a)
}

그리고 data를 v에 적용시키는 것이므로 v는 어떤 형태든 가능하도록 interface{}타입이어야 한다.

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    var a int8
    var b uint8
    var c rune
    var d string

    errA := json.Unmarshal([]byte("128"), &a)
    fmt.Println("errA?: ", errA)
    errB := json.Unmarshal([]byte("128"), &b)
    fmt.Println("errB?: ", errB)
    errC := json.Unmarshal([]byte("128"), &c)
    fmt.Println("errC?: ", errC)
    errD := json.Unmarshal([]byte("128"), &d)
    fmt.Println("errD?: ", errD)

    fmt.Println("a?: ", a)
    fmt.Println("b?: ", b)
    fmt.Println("c?: ", c)
    fmt.Println("d?: ", d)
}
// errA?:  json: cannot unmarshal number 128 into Go value of type int8
// errB?:  <nil>
// errC?:  <nil>
// errD?:  json: cannot unmarshal number into Go value of type string
// a?:  0
// b?:  128
// c?:  128
// d?:

128이라는 숫자는 int8의 범위를 넘으므로, 그리고 string이 아니므로 a,d에는 바인딩에 실패했다. 그러나 uint8rune에는 이를 적용할 수 있다. 즉 같은 숫자가 int8과 int32에 적용된 것이다. 그럼 string에 바인딩하기 위해서는? 문자열이어야 하므로 []byte(\""128\"") 였다면 가능했다.

태그를 사용한 의도 주입

태그는 구조체에만 붙일 수 있는 특별한 메타데이터라고 할 수 있다. 태그를 사용하면 마셜링과 언마셜링 과정에 우리가 개입할 수 있게 된다. json의 마셜링/언마셜링 관련된 태그 키는 json이며, 여기에 부여할 수 있는 값들은 아래와 같다.

`json:“FIELD_NAME[,omitempty,TYPE] || -”`

, 기준으로 대략 세 필드로 나눠볼 수 있는데 각각 다음과 같다.

  • FIELD_NAME: json 필드의 이름을 나타낸다. 필드에 붙인 이 이름이 json태그의 키값과 일치한다면 이를 대신 사용한다.
  • omitempty: 구조체에 값이 없는 경우(empty) 이를 생략(omit)한다.
  • TYPE: json의 값을 해당 type으로 변환한다.
  • -: 해당 필드를 숨긴다.

태그는 구조체에만 붙이는 것이 가능하므로 이 태그 또한 대상이 구조체일 때만 가능하다고 생각하면 된다. 아무튼 설명보다는 몇 줄의 코드가 더 이해하기 쉬우니 우선 FIELD_NAME부터 보자.

package main

import (
    "encoding/json"
    "fmt"
)

type Test struct {
    ID int `json:"id"`
    Body string `json:"body"`
}

func main() {
    a := Test{ID: 1, Body: "asdf"}
    b, _ := json.Marshal(a)
    fmt.Println("result?: ", string(b))
}
// result?:  {"id":1,"body":"asdf"}
// https://play.golang.org/p/6jHV5Ad4556

위와 같이 구조체와 태그가 명시되어 있다면 구조체의 값과 json의 값은 아래와 같다.

{"id":1,"body":"asdf"}

태그가 없었다면 아래와 같이 출력된다.

{"ID":1,"Body":"asdf"}

태그의 키로 FIELD_NAME body를 주었기 때문에 Body대신 body가 출력되었다. 그럼 언마셜링일 때는 어떻게 될까?

package main

import (
    "encoding/json"
    "fmt"
)

type Test struct {
    ID int `json:"id,omitempty"`
    Body string `json:"body,omitempty"`
}

func main() {
    a := []byte(`{"id":1,"body":"contents"}`)
    result := Test{}
    json.Unmarshal(a, &result)
    fmt.Println("result?: ", result)
}
// result?:  {1 contents}
// https://play.golang.org/p/ids8BqCxnw6

출력값은 아래와 같다.

{1 contents}

json 필드 idbody가 구조체에 잘 매핑된 것 같다. 만약 위 코드에서 a에 구조체의 태그와 다른 형태로 json을 전달하면?

...
...
type Test struct {
    ID int `json:"id,omitempty"`
    Body string `json:"body,omitempty"`
}

func main() {
        ...
        a := []byte(`{"ID":1,"BODY":"contents"}`)
        ...
}

음? 매칭이 안되어야 할 것 같지만 출력은 정상적으로 만들어진다.

{1 contents}

언마셜링의 경우 태그의 대소문자를 신경쓰지 않고 매칭한다. 아예 키가 다르다면 매칭하지 않는 것이다. 따라서 body가 BODY여도 상관없고 심지어 bOdY 이런식이어도 상관없이 매칭된다.

To unmarshal JSON into a struct, Unmarshal matches incoming object keys to the keys used by Marshal (either the struct field name or its tag), preferring an exact match but also accepting a case-insensitive match. By default, object keys which don’t have a corresponding struct field are ignored (see Decoder.DisallowUnknownFields for an alternative).

이렇게까지 키를 맞춰서 언마셜링을 해야 하는 이유를 잘 모르겠는데, 키가 다르면 그냥 에러 처리하는게 쉽지 않나? 어쨌든 코드를 보면 equalFold로 다시 같은지를 처리하는 모양이다.

이번에는 태그 omitempty에 대해서 정리해 보자. 이는 그냥 마셜링시 empty라면 omit해주는 역할을 한다. 언마셜링의 경우 매칭되는 키가 없다면 구조체에 기본값이 들어갈 것이므로 언마셜링 케이스에서 이 태그는 별 의미가 없다.

package main

import (
    "encoding/json"
    "fmt"
)

type Test struct {
    ID int `json:"id,omitempty"`
    Body string `json:"body,omitempty"`
}

func main() {
    a := Test{ID: 0, Body: ""}

    b, _ := json.Marshal(a)
    fmt.Println("result?: ", string(b))
}
// result?: {}
// https://play.golang.org/p/U_FF7nemqBM

출력값은 다음과 같다.

{}

Go의 타입에는 nil이 들어갈 수 없으므로 기본값이 있으며, 따라서 어떤 필드에 기본값이 있는 경우 마셜링시 이 필드를 생략해버린다. 이게 무슨 뜻이냐 하면 유저가 의도하여 어떤 int 필드에 0을 넣었다면, 이를 마셜링시 이 필드가 그냥 사라진다는 뜻이다. 이게 사용하면서 굉장히 불편한 부분 중 하나고, 따라서 omitempty는 잘 사용하지 않으려고 하는 편이다.

태그 TYPE은 json의 데이터 타입을 명시하는 역할을 한다. 이게 무슨 뜻이냐 하면

package main

import (
    "encoding/json"
    "reflect"
    "fmt"
)

type Test struct {
    ID int `json:"id,omitempty,string"`
    Body string `json:"body,omitempty"`
}

func main() {
    a := []byte(`{"id": "1", "body": "test"}`)
    b := Test{}
    json.Unmarshal(a, &b)
    fmt.Println("result?: ", b)
}

출력값은

{1 test}

json의 id는 문자열 "1"의 형태로 값이 지정되었지만 이를 json 태그 type애 명시하여 암묵적으로 Go의 int 타입으로 변환시켰다.

정리하면 다음과 같다.

  1. json태그의 첫 번째 필드는 변환에 필요한 필드 이름
  2. 두 번째 필드부터는 omitempty 혹은 변환할 타입에 대한 명시 가능

마지막으로는 태그 "-"가 있다. 이 경우 마셜링 대상에서 아예 제외한다. 단지 json으로 마셜링시 이를 제외하는 것이므로 필드 자체는 접근이 가능한데, 따라서 이를 아래 소개할 Marshal/Unmarshal을 통해 조합하여 다양한 방식으로 사용할 수 있다.

Marshaler/Unmarshaler 인터페이스 구현

태그는 구조체에만 붙일 수 있으므로 이를 소극적인 개입이라고 한다면, Marshaler, 혹은 Unmarshaler 인터페이스를 구현하여 더 적극적으로 마셜링 과정에 개입할 수 있다.

Marshaler

인터페이스의 정의는 아래와 같다.

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

태그와는 다르게 구조체가 아닌 타입이라도 MarshalJSON을 구현하기만 하면 Marshaler를 충족하므로 어떠한 타입이든간에 원하는 방식으로 마셜링할 수 있다. 물론 primitive, unnamed type에는 인터페이스의 구현을 붙일 수 없지만 이는 go에서 미리 다 구현해 두었으므로 상관없고, 커스텀한 마셜링이 필요하다면 타입을 새로 정의해서 처리하면 될 것이다.

실제로 어떻게 사용하는지 간단한 예를 들면 아래와 같다.

package main

import (
    "encoding/json"
    "fmt"
)

type CustomData string

func (c CustomData) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf(`{"customdata": "%s"}`, c)), nil
}

func main() {
    p := "teststring"
    withoutmarshaljson, _ := json.Marshal(p)

    c := CustomData("teststring")
    withmarshaljson, _ := json.Marshal(c)

    fmt.Println("string?: ", string(withoutmarshaljson))
    fmt.Println("customdata?: ", string(withmarshaljson))
}
// string?:  "teststring"
// customdata?:  {"customdata":"teststring"}
//
// https://play.golang.org/p/lHma6an78eq

실행 결과는 다음과 같다.

string?:  "teststring"
customdata?:  {"customdata":"teststring"}

CustomData 타입의 json 변환은 위처럼 json형태로 출력되도록 만들었다. json패키지 내 내부적인 구현을 생각해 보면, 파라미터로 넘어온 인터페이스가 Marshaler를 구현했다면 MarshalJSON을,, 없다면 단순 변환을 하도록 할 것이다.

주의할 점이 있다면 MarshalJSON의 구현 안에서 json.Marshal을 다시 자기 자신으로 하면 안된다. json.Marshal이 호출되면 Marshaler를 호출하는데, 거기서 다시 json.Marshal을 하면 재귀적으로 Marshaler를 호출하다 에러가 발생한다.

$ cat main.go
...
type CustomData string

func (c CustomData) MarshalJSON() ([]byte, error) {
    return json.Marshal(c)
}
...

$ go run main.go
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0200e03e0 stack=[0xc0200e0000, 0xc0400e0000]
fatal error: stack overflow

runtime stack:
runtime.throw(0x4dbb7d, 0xe)
    /home/cublr/.gvm/gos/go1.15.8/src/runtime/panic.go:1116 +0x72
runtime.newstack()
    /home/cublr/.gvm/gos/go1.15.8/src/runtime/stack.go:1067 +0x78d
...
...
...

따라서 구조체 안에서 json.Marshal을 호출해야 하는 경우 보통 다음과 같이 같은 구조의 익명 구조체를 사용하여 재귀를 피하도록 처리한다.

package main

import (
    "encoding/json"
    "fmt"
)

type TestStruct struct {
    String string
    Int    int
}

func (t TestStruct) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        String string
        Int    int
    }{
        String: t.String,
        Int:    t.Int,
    })
}

func main() {
    t := TestStruct{String: "string", Int: 1}
    result, _ := json.Marshal(t)

    fmt.Println("result?: ", string(result))
}

실행 결과는 다음과 같다.

$ go run main.go
result?:  {"String":"string","Int":1}

익명 구조체로 하는 이유는 TestStruct에 붙은 Marshaler구현을 사용하지 않으려는 것이다. 따라서 피할 수만 있다면 생각할 수 있는 다른 방법도 사용하는 것이 가능하다. 이를테면, 필드는 다 같은데 타입이 다른 새로운 구조체를 선언한다거나, 구조체의 각 필드를 돌면서 하나씩 json.Marshal을 한다거나…

Unmarshaler

언마셜러 인터페이스는 아래와 같이 정의한다.

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

실제로 어떻게 사용하는지 간단한 테스트는 다음과 같다.

package main

import (
    "encoding/json"
    "fmt"
    "strconv"
    "strings"
)

type TestStruct struct {
    String string
    Int    int
}

func (t *TestStruct) UnmarshalJSON(b []byte) error {
    s := string(b)
    trimmed := strings.Trim(s, "{}")
    for _, kv := range strings.Split(trimmed, ",") {
        splited := strings.Split(kv, ":")
        k := strings.Trim(splited[0], "\"")
        v := strings.Trim(splited[1], "\"")

        if k == "String" {
            t.String = v
        } else if k == "Int" {
            i, _ := strconv.ParseInt(v, 10, 32)
            t.Int = int(i)
        }
    }

    return nil
}

func main() {
    t := `{"String":"string","Int":1}`
    result := TestStruct{}
    json.Unmarshal([]byte(t), &result)

    fmt.Println("result?: ", result.String, result.Int)
}

Marshal과는 다르게 string을 파싱해야 하므로 조금 더 까다로운 것 같다. 주의사항으로는 위에서 Unmarshal에 대해 이야기했던 것처럼 Unmarshal은 값을 업데이트하는 작업이므로, UnmarshalJSON은 포인터 리시버로 구현하여야 한다. 또 Marshaler와 마찬가지로 자기 자신을 다시 호출하면 재귀호출이 발생하게 될 것이다.

특이한 형태의 json 컨트롤

같은 타입이면서 이름이 다르면?

구조체로 표현하지 못하는 json형태가 있다. 이를테면 json의 키가 타입이 아니라 이름을 표현하는 케이스가 바로 그렇다.

{
    "red": {
        "R": 255,
        "G": 0,
        "B": 0
    },
    "green": {
        "R": 0,
        "G": 255,
        "B": 0
    },
    "blue": {
        "R": 0,
        "G": 0,
        "B": 255
    }
}

이런 경우… 필드 이름이 고정이 아니므로 Go의 특정 타입에 이를 매핑할 수 없다. 따라서 단순 언마셔링이 불가능하다. 그래서 이런 케이스에서는 map을 사용하여 표현한다.

package main

import (
    "encoding/json"
    "fmt"
)

type RGB struct {
    Red   int `json:"R"`
    Green int `json:"G"`
    Blue  int `json:"B"`
}

type ColorIndex map[string]RGB

func main() {
    rawjson := `{"red": {"R": 255, "G": 0, "B": 0}, "green": {"R": 0, "G": 255, "B": 0}, "blue": {"R": 0, "G": 0, "B": 255}}`
    result := ColorIndex{}
    json.Unmarshal([]byte(rawjson), &result)

    fmt.Println("result?: ", result)
}

출력 결과는 다음과 같다.

$ go run main.go
result?:  map[blue:{0 0 255} green:{0 255 0} red:{255 0 0}]

맵이 너무 복잡한 경우를 생각해 보자. 파이썬이라면 dict를 통해 간단히 처리할 수 있지만 go에서는 쓰기도 힘든 map[string]map[string]string같은 걸로 처리해야 하므로 이게 매우 복잡한데, 깊이가 큰 맵을 처리하기 위해 mapstructure, 그리고 flat 같은 라이브러리를 사용하여 처리할 수 있겠다.

mapstructure의 경우 키에 해당 안되는 필드를 ,remain을 통해 한 필드의 맵으로 다 몰아넣을 수 있고, flat의 경우 구분자로 연결된 json의 키를 파싱해서 키를 적절한 깊이의 맵으로 변경하여 저장하도록 도와 준다.