Posted at

golangでのjsonのデコード

More than 5 years have passed since last update.

golang最近やっているので、いろいろぐぐったりJSON and Goなどを読みながらやってみました。

{

"period": "yy",
"exec_period": {
"start": {
"month": 1,
"week": 2,
"day": 3,
"hour": 4,
"minute": 5
},
"end": {
"month": 6,
"week": 7,
"day": 8,
"hour": 9,
"minute": 10
}
},
"backup": [
{
"local_dir": "directoryLo1",
"server_dir": "directoryLo2",
"server_host": "domaineName"
},
{
"local_dir": "directoryLo1",
"server_dir": "directorySe2",
"server_host": "domaineName"
}
],
"incremental_save": "1Y2M"
}

こんな感じのjsonがあるとします。

これを読み込むには標準ライブラリのencoding/jsonを使ってこんな感じに書けます。

package main

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

type time struct {
Month int
Week int
Day int
Hour int
Minute int
}

type execPeriod struct {
Start time
End time
}

type directories struct {
LocalDir string `json:"local_dir"`
ServerDir string `json:"server_dir"`
ServerHost string `json:"server_host"`
}

type data struct {
Period string
ExecPeriod execPeriod `json:"exec_period"`
Backup []directories
IncrementalSave string `json:"incremental_save"`
}

func main() {
dec := json.NewDecoder(os.Stdin)
var d data
dec.Decode(&d)
fmt.Printf("%+v\n", d)
}


result

$ ./json < test.json

{Period:yy ExecPeriod:{Start:{Month:1 Week:2 Day:3 Hour:4 Minute:5} End:{Month:6 Week:7 Day:8 Hour:9 Minute:10}} Backup:[{LocalDir:directoryLo1 ServerDir:directoryLo2 ServerHost:domaineName} {LocalDir:directoryLo1 ServerDir:directorySe2 ServerHost:domaineName}] IncrementalSave:1Y2M}

json側と同じようにstructを定義して、json.Decoderに渡してあげればおkっぽいです。ただしkeyの名前がjson側と違う場合は、json:"key_name"みたいにして違いを吸収してあげる必要があります。

JSON and Goの後ろの方に、jsonの構造がわからない場合のやり方も載っていて、interfaceを使いなよ!的なことが書いてあります。interfaceよくわかってないので調べつつやったのが以下です。

package main

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

func assert(data interface{}) {
switch data.(type) {
case string:
fmt.Print(data.(string))
case float64:
fmt.Print(data.(float64))
case bool:
fmt.Print(data.(bool))
case nil:
fmt.Print("null")
case []interface{}:
fmt.Print("[")
for _, v := range data.([]interface{}) {
assert(v)
fmt.Print(" ")
}
fmt.Print("]")
case map[string]interface{}:
fmt.Print("{")
for k, v := range data.(map[string]interface{}) {
fmt.Print(k + ":")
assert(v)
fmt.Print(" ")
}
fmt.Print("}")
default:
}
}

func main() {
var data interface{}
dec := json.NewDecoder(os.Stdin)
dec.Decode(&data)
assert(data)
fmt.Println()
}


result

$ ./json < test.json

{period:yy exec_period:{start:{month:1 week:2 day:3 hour:4 minute:5 } end:{month:6 week:7 day:8 hour:9 minute:10 } } backup:[{local_dir:directoryLo1 server_dir:directoryLo2 server_host:domaineName } {local_dir:directoryLo1 server_dir:directorySe2 server_host:domaineName } ] incremental_save:1Y2M nulltest:null }

interfaceすげー

Effective GoのType switchの例まんまなのですが、型アサーションの結果によって処理を振り分けることができます。ここの説明によるとDecode結果は、bool、float64、string、[]interface{}、map[string]interface{}、nilのどれかになるはずなので、一旦interface{}で受けてから、それぞれに対応した処理に振り分けます。

実際使うときはstruct定義した方があとあと使いやすいのでいい気がしますが、golangのinterfaceはこんなの以外にもいろいろ出来て面白そうですね。