goに限った話ではないが、複雑なJSONから一部だけ取り出したいといった場合に、その複雑なJSONのフォーマットを構造体として定義しておくのは面倒だ。そこで型を決めずmap[string]interface{}
に流し込んでおいて、あとでType-castingすれば取り出せるのだが、値が多段になっている場合など、なかなか書き方を調べるのが大変だったので、とりあえず動いたものを公開する。
対象となるJSONファイルは、このように直下にkey:valueがあるものや、valueが多段になっているもの、配列になっているものなどを想定している。
sample.json
{
"name": "Hello",
"value1": {
"name":"Hello1"
},
"value2":[
{"name":"Hello2-1"},
{"name":"Hello2-2"}
]
}
それに対して、コマンドラインでキーワードを指定して、
jsontest ファイル名 キーワード1 キーワード2
といった使い方をする。
jsontest.go
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
)
func main() {
flag.Parse()
args := flag.Args()
var fname string
if len(args) > 0 {
fname = args[0]
} else {
log.Fatal("usage: jsontest fname [key1] [key2]")
}
fileContent, err := ioutil.ReadFile(fname)
if err != nil {
log.Fatal("ReadFile Error")
}
fileString := string(fileContent)
var jsonContent map[string]interface{}
if err := json.Unmarshal([]byte(fileString), &jsonContent); err != nil {
log.Fatal("Unmarshal Error")
}
if len(args) > 1 {
var value1 interface{} = jsonContent[args[1]]
switch t := value1.(type) {
case string:
fmt.Println(t)
case interface{}: // valueの下にkey:valueがある場合(sample.jsonのvalue1)
if len(args) > 2 {
value2, ok := value1.(map[string]interface{})
if ok {
fmt.Println(value2[args[2]])
}
} else {
fmt.Println(value1)
}
case []interface{}: // valueの下に配列がある場合(sample.jsonのvalue2)
for _, value1 := range value1.([]interface{}) {
if len(args) > 2 {
value2, ok := value1.(map[string]interface{})
if ok {
fmt.Println(value2[args[2]])
}
} else {
fmt.Println(value1)
}
}
default:
fmt.Println("default")
fmt.Printf("%T ", t)
fmt.Println(t)
}
} else { // 検索キーワードがない場合、そのまま表示する
fmt.Println(fileContent)
}
}
言うまでもないが、取り出すキーが決まってる場合はswitchで型ごとに分岐するのではなく一気に指定すればいい。