Goのencoding/json.Decoderが構造体フィールドにjson:"-"タグがついていたら無視する処理を探してみた

## コンテキスト
- Goの構造体のフィールドには"tag_name:tag_value"というようにタグをつけることができる。
- jsonのエンコードやデコードで使われるjsonタグというものがある。
- json.Marshalで構造体->jsonのエンコードする場合に、構造体のフィールドのjsonタグに紐づいた文字列がjsonにおける文字列となる。
- jsonタグに"-"という値を紐付けると、jsonには出力されないフィールドとなる。

```go
type Struct struct {
    Name        string `json:"name"`
    Age         int
    Credential  string  `json:"-"`
}
```

上の構造体は以下のようなjsonに変換されてエンコードされる。

```json
{"name": "Tomori Yu", "Age": 21}
```

## 疑問
- エンコードに関する挙動は以上に見た通りだが、デコードの挙動はどうなっているのだろう。

## 試してみる

```go
package main

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

type person struct {
	Name       string `json:"name"`
	Age        int
	Credential string `json:"-"`
}

func main() {
	p1 := person{"yu", 21, "AKIi9854u4t8394j8gf"}
	bs1, _ := json.Marshal(p1)
	fmt.Printf("marshaled person: %v\n", string(bs1))
    // -> marshaled person: {"name":"yu","Age":21}

	var p2 person
	_ = json.Unmarshal(bs, &p2)
	fmt.Printf("unmarshaled person: %v\n", p2)
    // -> unmarshaled person: {yu 21 }

	bs2 := []byte(`{"name": "yu", "Age": 21, "Credential": "AKIi9854u4t8394j8gf"}`)
	decoder := json.NewDecoder(bytes.NewBuffer(bs2))
	var p3 person
	_ = decoder.Decode(&p3)
	fmt.Printf("decoded person: %v\n", p3)
    // -> decoded person: {yu 21 }
}
```

- json.MarshalではAgeがアウトプットに残るが、json.Unmarshal&json.DecodeではAgeが残らない。
- 構造体->jsonの場合と同じように、json->構造体のデコードにおいても構造体のフィールドにjson:"-"というタグが付いていれば無視される。

## json.Decoderに関して構造体のフィールドにjson:"-"というタグが付いていたら無視する処理を探してみた

### encoding/json.Decoder

```go
decoder := json.NewDecoder(buffer)
```

json.NewDecoderはDecoder構造体を返す。

[Decoder](https://github.com/golang/go/blob/master/src/encoding/json/stream.go#L14-L25)

```go
type Decoder struct {
	r       io.Reader
	buf     []byte
	d       decodeState
	scanp   int   // start of unread data in buf
	scanned int64 // amount of data already scanned
	scan    scanner
	err     error

	tokenState int
	tokenStack []int
}
```

Decoder構造体のポインタ型に対するDecodeメソッドは引数として渡した変数にデコードした結果を返す。
[Decoder.Decode](https://github.com/golang/go/blob/master/src/encoding/json/stream.go#L49-L79)

```go
func (dec *Decoder) Decode(v interface{}) error {
	if dec.err != nil {
		return dec.err
	}

	if err := dec.tokenPrepareForDecode(); err != nil {
		return err
	}

	if !dec.tokenValueAllowed() {
		return &SyntaxError{msg: "not at beginning of value", Offset: dec.InputOffset()}
	}

	// Read whole value into buffer.
	n, err := dec.readValue()
	if err != nil {
		return err
	}
	dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
	dec.scanp += n

	// Don't save err from unmarshal into dec.err:
	// the connection is still usable since we read a complete JSON
	// object from it before the error happened.
	err = dec.d.unmarshal(v)

	// fixup token streaming state
	dec.tokenValueEnd()

	return err
}
```

dec.dは、Decoder構造体のフィールドであり、decodeState構造体のインスタンスである。
dec.d.initでdecodeState.dataにまだ読まれてない[]byteを初期化している。
そして、err = dec.d.unmarshal(v)に、デコードの処理がありそうだ。
[decodeState](https://github.com/golang/go/blob/master/src/encoding/json/decode.go#L204-L221)

```go
type decodeState struct {
	data         []byte
	off          int // next read offset in data
	opcode       int // last read result
	scan         scanner
	errorContext struct { // provides context for type errors
		Struct     reflect.Type
		FieldStack []string
	}
	savedError            error
	useNumber             bool
	disallowUnknownFields bool
}
```

[decodeState.unmarshal](https://github.com/golang/go/blob/master/src/encoding/json/decode.go#L170-L185)

```go
func (d *decodeState) unmarshal(v interface{}) error {
	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return &InvalidUnmarshalError{reflect.TypeOf(v)}
	}

	d.scan.reset()
	d.scanWhile(scanSkipSpace)
	// We decode rv not rv.Elem because the Unmarshaler interface
	// test must be applied at the top level of the value.
	err := d.value(rv)
	if err != nil {
		return d.addErrorContext(err)
	}
	return d.savedError
}
```

d.scanは、decodeState構造体のscanフィールドであり、scanner構造体のフィールドである。
scannerはjsonをパースする処理におけるステートマシンとして利用される。
d.scan.reset()でdecodeStateのフィールドであるscannerインスタンスが初期化される。
d.value(rv)でデコード処理が行われていそうだ。
[scanner](https://github.com/golang/go/blob/master/src/encoding/json/scanner.go#L52-L83)

```go
// A scanner is a JSON scanning state machine.
// Callers call scan.reset and then pass bytes in one at a time
// by calling scan.step(&scan, c) for each byte.
// The return value, referred to as an opcode, tells the
// caller about significant parsing events like beginning
// and ending literals, objects, and arrays, so that the
// caller can follow along if it wishes.
// The return value scanEnd indicates that a single top-level
// JSON value has been completed, *before* the byte that
// just got passed in.  (The indication must be delayed in order
// to recognize the end of numbers: is 123 a whole value or
// the beginning of 12345e+6?).
type scanner struct {
	// The step is a func to be called to execute the next transition.
	// Also tried using an integer constant and a single func
	// with a switch, but using the func directly was 10% faster
	// on a 64-bit Mac Mini, and it's nicer to read.
	step func(*scanner, byte) int

	// Reached end of top-level value.
	endTop bool

	// Stack of what we're in the middle of - array values, object keys, object values.
	parseState []int

	// Error that happened, if any.
	err error

	// total bytes consumed, updated by decoder.Decode (and deliberately
	// not set to zero by scan.reset)
	bytes int64
}
```

[decodeState.value](https://github.com/golang/go/blob/master/src/encoding/json/decode.go#L353-L390)

```go
// value consumes a JSON value from d.data[d.off-1:], decoding into v, and
// reads the following byte ahead. If v is invalid, the value is discarded.
// The first byte of the value has been read already.
func (d *decodeState) value(v reflect.Value) error {
	switch d.opcode {
	default:
		panic(phasePanicMsg)

	case scanBeginArray:
		if v.IsValid() {
			if err := d.array(v); err != nil {
				return err
			}
		} else {
			d.skip()
		}
		d.scanNext()

	case scanBeginObject:
		if v.IsValid() {
			if err := d.object(v); err != nil {
				return err
			}
		} else {
			d.skip()
		}
		d.scanNext()

	case scanBeginLiteral:
		// All bytes inside literal return scanContinue op code.
		start := d.readIndex()
		d.rescanLiteral()

		if v.IsValid() {
			if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil {
				return err
			}
		}
	}
	return nil
}
```

v.IsValid()はvがゼロ値でなければtrueを返します。
構造体を引数に渡した場合は、d.object(v)が呼ばれるっぽいです。

decodeState.object()を読むと、[switch文で引数のunderlyingな型がStructな時の処理](https://github.com/golang/go/blob/master/src/encoding/json/decode.go#L650)が書かれてあります。

```go
	case reflect.Struct:
		fields = cachedTypeFields(t)
		// ok
```

fieldsはstructFields構造体のインスタンスです。
[structFields](https://github.com/golang/go/blob/master/src/encoding/json/encode.go#L727-L730)

```go
type structFields struct {
	list      []field
	nameIndex map[string]int
}
```

cachedTypeFields関数は、typeFields関数にキャッシュ機能を足した薄いラッパーとなっています。
typeFieldsは以下のようになっています。
https://github.com/golang/go/blob/master/src/encoding/json/encode.go#L1209-L1391

```go
// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func typeFields(t reflect.Type) structFields {
	// Anonymous fields to explore at the current level and the next.
	current := []field{}
	next := []field{{typ: t}}
[省略]
				tag := sf.Tag.Get("json")
				if tag == "-" {
					continue
				}
[省略]
```

ありました。sfは構造体のフィールドです。
forの中でcontinueされなかったフィールドはforブロックの後の処理でfields = append(fields, field)という感じでフィールドとして登録されます。
構造体のフィールドのうちjsonタグに"-"が付いているものはcontinueされているので、無視することになっているみたいです。

先ほど見たdecodeState.object()ではfields = cachedTypeFields(t)という感じで構造体の有効なフィールドだけが処理されることになります。
その後にfields変数に関して、以下の処理が書かれています。
https://github.com/golang/go/blob/master/src/encoding/json/decode.go#L696-L709

```go
			if i, ok := fields.nameIndex[string(key)]; ok {
				// Found an exact name match.
				f = &fields.list[i]
			} else {
				// Fall back to the expensive case-insensitive
				// linear search.
				for i := range fields.list {
					ff := &fields.list[i]
					if ff.equalFold(ff.nameBytes, key) {
						f = ff
						break
					}
				}
			}
```

jsonのkeyにマッチする有効なStructのフィールドが見つかればfという変数に入れられて後の処理に続くようです。

## まとめ

- json.Decodeもjson:"-"タグのついた構造体のフィールドは無視する。