【Golang】jsonの内容をデコードする

前回のプログラムを改良してserverのIPとポート番号をハードコーディングではなくjsonファイルから取得できるようにしました。


【Golang】Socket通信するプログラムを書いてみた - くどはむと猫の窓

 

今回参考にしたサイトは以下です。

 

 

 

前回まではgolang.jpの方をまず最初に参考にしていたのですが、よく読んでいるとgolang.jpのosパッケージのOpen関数シグネチャgolang.orgのと違っていたりと、ちょっと信頼できるか怪しい?のでgolang.orgの方をまず最初に参考にするように変えました。

それにしてもこれは情報が古いのか、OpenFile関数と間違えているのか、どうなんでしょう。

 

話を戻します。

jsonパッケージのtype Decoderのところにコード例が載っています。

その中で使われている関数でまず目につくのはこれ

func NewDecoder(r io.Reader) *Decoder

NewDecoder関数はioパッケージのReaderインターフェースを実装している型の変数を引数に取り、Decoder型(のポインタ変数)を返すようです。

 

その次に目につくのはこれ

func (dec *Decoder) Decode(v interface{}) error

さっき返ってきたDecoderをレシーバとするDecode関数で、引数は空インターフェースです。つまりGoの型ならなんでも引数にしていいというわけですね。何それ。

「Unmrshalのところも読めよ」と書いてあるので読んでみると、jsonの型は以下の6つのどれかになるはずだから、この中でベストな型にデコードする的なことが書かれています。

bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null

またUnmarshalについてはJSON and Goにも書いてあり、ここでやっと「ああ、これと同じようなことが起きているのか」と分かりました。

type Decoderのところの例ではjsonの構造に合わせた構造体を定義し、その型の変数(ポインタ)を引数として渡していますが、これは引数として渡された構造体の変数名などの情報をもとに、型アサーションした結果が入れられているという感じでしょう。

JSON and Go下の方に例が載っていますが、構造体を用意しなくても自分で型アサーションを使って値を振り分ける関数を書けば同じようなことができるようです。interface便利。

とはいえ面倒なので例の通り構造体を使うことにしました。

 

ところで、NewDecoderの引数となっているReaderインターフェースはどのようなものでしょうか?そのままリンク先に飛ぶと

type Reader interface {
        Read(p []byte) (n int, err error)
}

となっています。

このRead関数を実装している型ならよいのですね。

僕はjsonデータをjsonファイルから読みたいと思っていますが、NewDecoderに渡せるでしょうか?まずは読みたいファイルを開く関数を探します。

osパッケージのOpen関数がそれっぽいです。

func Open(name string) (file *File, err error)

この関数は読み込んだファイルのファイルディスクリプタをFile型の変数として返します。

このFile型については同じosパッケージ内に書かれています。

そのまま読み進めていくと...

func (f *File) Read(b []byte) (n int, err error)

ありました。ReaderインターフェースのRead関数と同じシグネチャです。

ということはFile型はReaderインターフェースを実装しているのでこれをNewDecoderに渡すことができそうですね。

 

まとめると以下のような順序になります。

  1. jsonと同じ構造の構造体を定義する
  2. Open関数でjsonファイルをオープン
  3. Open関数で得たファイルディスクリプタをNewDecoderに渡す
  4. NewDecoderのDecode関数を呼び出す(引数は構造体の変数)

 

ということでここまでの情報を元に改良したプログラムが以下になります。

サーバ側

サーバ側のjsonはシンプルです。

{

  "Port": "55555"
}

 

 

クライアント側

クライアント側のjsonはこんな感じです。

{

  "server": {
    "IP": "192.xxx.xxx.xxx",
    "Port": "55556"
  }
}

 

これでjsonから設定情報を読み込むということができるようになりました。

他にもjsonに追加したくなったら構造体を修正してやればいいだけなので簡単ですね。

 

前回のプログラムではクライアントは自分のIPやポート番号も入力しなければなりませんでしたが、 DialTCP関数の第2引数をnilにすれば自動でローカルのIPと(空いてる)ポートを取ってくれるようなのでnilに変更しました。

 

次の目標は、今サーバ用もクライアント用もコマンドを打ったディレクトリからjsonファイルを探すのでこれをどこから呼び出しても特定のディレクトリから探すようにすること、そして通信を暗号化して送るように改良したいなと思っています。