平常運転

アニソンが好き

過去記事とかは記事一覧で見れます

Go で JSON を扱うにあたっての個人的なメモ

個人的なメモです。というのは全てにおいて予防線として扱われる。

omitempty

JSON tag につける omitempty は、 JSON の Marshal 時に参照される。 empty value だった際にそのフィールドがまるごとスキップされる。

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

https://golang.org/pkg/encoding/json/#Marshal

(なので、 UnMarshal にしか使わないような構造体の JSON tag につけても意味はない)

Unmarshal 時のフィールド名の対応

JSON のあるフィールドが JSON のどのフィールドに対応づけられるかのルール:

How does Unmarshal identify the fields in which to store the decoded data? For a given JSON key "Foo", Unmarshal will look through the destination struct's fields to find (in order of preference):

  • An exported field with a tag of "Foo" (see the Go spec for more on struct tags),
  • An exported field named "Foo", or
  • An exported field named "FOO" or "FoO" or some other case-insensitive match of "Foo".
https://blog.golang.org/json-and-go

つまり、(Unmarshal する都合においてのみ言うと)、JSON 上で hogeHoge というフィールドのものを構造体の HogeHoge や Hogehoge などにマッピングしたい場合、 JSON タグはつけなくてもマッピングされる。
逆に、この Case-Insensitive match を無効にするオプション今のところないようなので、 Case-sensitive な厳格な JSON デコーダは実現できないことになる。今触ってるコードだとこれが結構キツいんだけどどうしようか悩んでいる。諦める???

未知のフィールドを除外する

func (*Decoder) DisallowUnknownFields() を使う。

DisallowUnknownFields causes the Decoder to return an error when the destination is a struct and the input contains object keys which do not match any non-ignored, exported fields in the destination.

https://golang.org/pkg/encoding/json/#Decoder.DisallowUnknownFields

ちなみに前述したような Case-insensitive match が無効になる訳ではない。

missing key

package main

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

func main() {
	var data struct {
		Name      string   `json:"name"`
		Memo      string   `json:"memo"`
		Nicknames []string `json:"nicknames"`
	}
	buffer := bytes.NewBufferString("{\"name\":\"tom\"}")
	decoder := json.NewDecoder(buffer)
	if err := decoder.Decode(&data); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(data)
}

https://play.golang.org/p/PtPrJd0TIE0

構造体の期待するフィールドが JSON 側になかったとき、デコーダは特にエラーにならずに empty value を自然と埋める。緩く扱うにはメリットだけれど、ユーザ入力をきちんとハンドリングしたい時にはちょっと困る気もする。

null as array

package main

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

func main() {
	var data struct {
		Name      string   `json:"name"`
		Nicknames []string `json:"nicknames"`
	}
	buffer := bytes.NewBufferString("{\"name\":\"tom\", \"nicknames\": null}")
	decoder := json.NewDecoder(buffer)
	if err := decoder.Decode(&data); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(data)
}

https://play.golang.org/p/go69_sBuVZ1

リストっぽいところに null を渡すと空のリストになる。上記の missing key と組み合わせると、ふつうにやると以下の3つは Unmarshal された構造体からは区別できない

  • null
  • []
  • (そもそもフィールドが渡されてこなかった)

2018/09/04 01:19: 複数のフィールドが同じタグを持っている

package main

import (
	"encoding/json"
	"fmt"
)

type Single struct {
	Name1 string `json:"name"`
	Name2 string `json:"name"`
}

type Inner1 struct {
	Name1 string `json:"name"`
}

type Inner2 struct {
	Name2 string `json:"name"`
}

type Inner3 struct {
	Name3 string `json:"name3"`
}

type OuterA struct {
	*Inner1
	*Inner2
}

type OuterB struct {
	*Inner1
	*Inner3
}

func main() {
	data := &Single{}
	if err := json.Unmarshal([]byte("{\"name\":\"tom\"}"), &data); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(data)

	dataA := &OuterA{}
	if err := json.Unmarshal([]byte("{\"name\":\"tom\"}"), &dataA); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(dataA.Inner1, dataA.Inner2)

	dataB := &OuterB{}
	if err := json.Unmarshal([]byte("{\"name\":\"tom\"}"), &dataB); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(dataB.Inner1, dataB.Inner3)
}

https://play.golang.org/p/2Ovn3J7ChfQ

複数のフィールドにうっかり同じタグをつけてしまった場合、どちらにも Unmarshal されない。ちなみに playground つけてないけど Marshal もされない気がする。
上記の Playground の `Single` のように単一の struct にタグが複数ついてると go vet が怒ってくれるけれど、 Outer~ のように embedding だと go vet でも気付かないので難しい。(過去にこれでミスったことがある)

THE IDOLM@STER CINDERELLA GIRLS new generations★Brilliant Party! に行ってきた

アイドルマスターのライブといえばキャストの声優さんが歌い踊る方のリアルライブを普段は指すのだけれど、最近はキャラクター達が歌い踊るバーチャルライブの試みも幾つか生まれている。そのうちの一つの、シンデレラガールズの new generations 主演のバーチャルライブを見に行ってきた。

vrzone-pic.com

f:id:astj:20180902130738j:plain

まだ期間中なので一応具体的なセトリの話はしないでおこうと思います。期間が終わったら追記するかも。

続きを読む

コカ・コーラ SUMMER STATION 音楽LIVE で Poppin'Party を見てきた

コカ・コーラ SUMMER STATION 音楽LIVE というイベントが7~8月にあり、そのうち8/8に Poppin'Party が出演するということで見てきた。

www.tv-asahi.co.jp

当日は関東に台風が接近し、開催がギリギリまで危ぶまれていたけど無事開催できて本当によかった。Poppin'Party のライブでは久々のオルスタらしいオルスタで間近な距離感の中、曲数的にも充実感がある楽しいライブだった。

続きを読む

体重を計り始めた

最近同僚から Withings のハイテク体重計を譲り受けた。何がハイテクかというと、体重や体脂肪率、脈拍などを自動的にサーバーに送信して、スマホアプリからそれらのスタッツの変化を確認することができる。

あと IFTTT にも対応してるので、(体重だけになってしまうけど) iOS のヘルスケアアプリに結果を流し込むこともできる。専ら関心があるのは体重なので普段はこっちで見ている。

一人暮らしをして4年ちょっとになるけど、これまでは体重計を持ってなかったから体重といえば銭湯か健康診断でたまに計ってふーん、となるだけだった。今回体重計を手に入れたことで日々の体重の変化を追跡できるようになった。何しろズボラなので自分の体重がどう変化しているかなんて覚えられないし能動的に記録する気もないのだけど、これなら寝る前に体重計に乗るだけで勝手にアプリに記録されていくので助かっている。

それにしても、思ったより体脂肪率が低かったりそもそも体重もちょっと軽かったので、これはどうしたものかという感じもある。これ以上体重が減っても全く嬉しくないし、さりとて贅肉で増えてもそれはそれで嬉しくない。筋肉で増量できると一番幸せなのだけど、今の所特に努力するつもりもないので自然に身を任せて体重の変化を見守っていきたい。

29歳

なった。前日まで海外(上海)に遊びに行っており、帰りの飛行機が遅延した結果家へ帰る阪急電車の中で7/23を迎えることになった。

去年はこれ。

astj.hatenablog.com

所感は去年と似ている気がするけれど、なんとなく1年(特に後半)で仕事で考えることの目線が少し変わったような、別にそうでもないような……という感じはしている。まあ働き出して5年目ともなればそういうこともあるだろう。オタクとしては年々極まってきている自覚があり、身を壊さない程度に遊んでいきたいですね、と思っている。あと楽器を弾く機会になんだかんだと恵まれたし、どうやら29歳も恵まれそうである。

そうそう、その上海にいる間に向こうの人に「学生ですか?」とか「学生さんだと思った」と言われる機会があり、若く見られて喜ぶという訳でもなく、年相応というか経歴相応の社会性がないのだなということを思ったりした……

泣いても笑っても二十代最後の1年なので遊んで駆け抜けようと思います。

docker-compose で - や _ で始まるプロジェクト名を使ってはいけない

こういうなんでもないプロジェクトがあったとする。

[astj@gemmy01 /Users/astj/sandbox/myproject]$ cat Dockerfile
FROM debian:8
[astj@gemmy01 /Users/astj/sandbox/myproject]$ cat docker-compose.yml
version: "3"

services:
  debian:
    build:
      context: .
      dockerfile: Dockerfile
[astj@gemmy01 /Users/astj/sandbox/myproject]$ docker-compose build
Building debian
Step 1/1 : FROM debian:8
 ---> ce40fb3adcc6
Successfully built ce40fb3adcc6
Successfully tagged myproject_debian:latest

ここで docker-compose のプロジェクト名を - や _ で始まるものにすると、直感的じゃないエラーメッセージを吐いて失敗する。

[astj@gemmy01 /Users/astj/sandbox/myproject]$ docker-compose -p -myproject build
Building debian
ERROR: invalid reference format
[astj@gemmy01 /Users/astj/sandbox/myproject]$ docker-compose -p _myproject build
Building debian
ERROR: invalid reference format

verbose オプションをつけて辿ると、 Docker API から 500 が帰ってきていることが分かる。そうだよねという感じ。

[astj@gemmy01 /Users/astj/sandbox/myproject]$ docker-compose -p -myproject --verbose build
compose.config.config.find: Using configuration files: ./docker-compose.yml
docker.utils.config.find_config_file: Trying paths: ['/Users/astj/.docker/config.json', '/Users/astj/.dockercfg']
docker.utils.config.find_config_file: Found file at path: /Users/astj/.docker/config.json
docker.auth.load_config: Found 'auths' section
(snip)
docker.auth.load_config: Found 'credsStore' section
urllib3.connectionpool._make_request: http://localhost:None "GET /v1.25/version HTTP/1.1" 200 566
compose.cli.command.get_client: docker-compose version 1.21.1, build 5a3f1a3
docker-py version: 3.3.0
CPython version: 3.6.4
OpenSSL version: OpenSSL 1.0.2o  27 Mar 2018
compose.cli.command.get_client: Docker base_url: http+docker://localhost
compose.cli.command.get_client: Docker version: Platform={'Name': ''}, Components=[{'Name': 'Engine', 'Version': '18.03.1-ce', 'Details': {'ApiVersion': '1.37', 'Arch': 'amd64', 'BuildTime': '2018-04-26T07:22:38.000000000+00:00', 'Experimental': 'true', 'GitCommit': '9ee9f40', 'GoVersion': 'go1.9.5', 'KernelVersion': '4.9.87-linuxkit-aufs', 'MinAPIVersion': '1.12', 'Os': 'linux'}}], Version=18.03.1-ce, ApiVersion=1.37, MinAPIVersion=1.12, GitCommit=9ee9f40, GoVersion=go1.9.5, Os=linux, Arch=amd64, KernelVersion=4.9.87-linuxkit-aufs, Experimental=True, BuildTime=2018-04-26T07:22:38.000000000+00:00
compose.cli.verbose_proxy.proxy_callable: docker inspect_network <- ('myproject_default')
urllib3.connectionpool._make_request: http://localhost:None "GET /v1.25/networks/myproject_default HTTP/1.1" 404 50
compose.service.build: Building debian
compose.cli.verbose_proxy.proxy_callable: docker build <- (path='/Users/astj/sandbox/myproject', tag='-myproject_debian', rm=True, forcerm=False, pull=False, nocache=False, dockerfile='Dockerfile', cache_from=None, labels=None, buildargs={}, network_mode=None, target=None, shmsize=None, extra_hosts=None, container_limits={'memory': None}, gzip=False, isolation=None, platform=None)
docker.api.build._set_auth_headers: Looking for auth config
(snip)
urllib3.connectionpool._make_request: http://localhost:None "POST /v1.25/build?t=-myproject_debian&q=False&nocache=False&rm=True&forcerm=False&pull=False&dockerfile=Dockerfile HTTP/1.1" 500 39
compose.cli.verbose_proxy.proxy_callable: docker build -> <generator object APIClient._stream_helper at 0x1049ba570>
ERROR: compose.cli.errors.log_api_error: invalid reference format

明示的にこういうプロジェクト名をつけることはないと思うけど、 docker-compose のプロジェクト名のデフォルトはカレントディレクトリの basename から決まるので、例えばディレクトリ名が - で始まる場合も同じ状況になる。

[astj@gemmy01 /Users/astj/sandbox/-myproject]$ docker-compose build
Building debian
ERROR: invalid reference format

(逆に、この場合でも安全なプロジェクト名を明示的に渡せばきちんと動く。)

[astj@gemmy01 /Users/astj/sandbox/-myproject]$ docker-compose -p mymyproject build
Building debian
Step 1/1 : FROM debian:8
 ---> ce40fb3adcc6
Successfully built ce40fb3adcc6
Successfully tagged mymyproject_debian:latest

ディレクトリ名が - で始まることも普段そうそうないような気がするが、たとえば Jenkins ではビルドプロジェクトの名前に - が入っていると、 truncate された結果こういうディレクトリが出現することがある。。以下のディレクトリは、Hoge-Foobar プロジェクトの様子である。つまり今回ハマった実例……

drwxr-xr-x 14 jenkins jenkins 4096 Jun 14 00:22 -Foobar_better_dockerfile-7R67AOHUZ25GOP2ZOFPGO2JCU7R5KFETCWFFHQJGSXNWUUACIIAA

原因が分かってしまえば納得感があるのだけれど、エラーメッセージがわかりにくいし、 docker-compose がバリデーションしてくれる訳でもないし、さらにカレントディレクトリが暗黙に使われていると非常に気付きづらい。気をつけましょう。。

YURIKA ENDO FINAL LIVE-Emotional Daybreak- に行ってきた

表題の通り、今年の5月いっぱいで引退した遠藤ゆりかさんのファイナルライブが6/1にあって、行ってきた。感想をどう文章にしようかずいぶん迷ったのだけれど、言葉足らずでも何か書いておこうかなと思ったので書いておこうと思う。

yurika-endo.com

続きを読む